From 2e76b5c181463853deb0fa8d6a5ab95133b2cfeb Mon Sep 17 00:00:00 2001 From: landgreen Date: Sun, 9 Apr 2023 08:29:59 -0700 Subject: [PATCH] surface plasmons retrocausality drains 30% less energy as time rewinds, but each time you start to rewind you drain 30 energy no longer provides immunity for 1 second after exiting rewind spawns 20% fewer bots deflecting changes shielded mobs take 50% more energy to deflect deflecting shielded mobs now only disables your shield for perfect diamagnetism and not for very long you can deflect any mob if you have at least 5% energy, but if deflecting brings you below that your shield is disabled for 1 second also you can't passively regen energy while shield is disabled standing wave rework coupling gives iceIX -> max energy expansion gives deflection efficiency -> +50 max energy spherical harmonics gives 40 -> 50% deflection efficiency electronegativity is a fieldTech for standing wave, wormhole, and pilot wave also 0.15 -> 0.22% damage per energy dynamic equilibrium is no longer unlocked by standing wave tech: surface plasmons - after deflecting drains all your energy, shoot lasers in every direction added a generic system to add code that loops for a set time Example code: simulation.ephemera.push({ name: "uniqueName", count: 60, //cycles before it self removes do() { this.count-- if (this.count < 0) simulation.removeEphemera(this.name) //run code here }, }) some bug fixes also new bugs probably --- img/surface plasmons.webp | Bin 0 -> 48858 bytes js/bullet.js | 2 +- js/level.js | 2280 ++++++++++++++++++++++++++++++++++++- js/player.js | 42 +- js/tech.js | 171 +-- todo.txt | 51 +- 6 files changed, 2413 insertions(+), 133 deletions(-) create mode 100644 img/surface plasmons.webp diff --git a/img/surface plasmons.webp b/img/surface plasmons.webp new file mode 100644 index 0000000000000000000000000000000000000000..a78ba3a8162faf29fa3cc4bc7506ae585c6530c6 GIT binary patch literal 48858 zcmV(!K;^$uNk&G}z5oDMMM6+kP&gpQz5oC)M**DyDu4k10X_r)OaK4~mZpmd0-)IO zh>wC3tYW42`CrCvz3hqGe;Yq;KgN8PR9~zAZvPeihx>Q?Px5~S;BbU)X>)Bno<3I1>Yx6mi@ zf9e14zc&7Bf98Mr_bdMm{{#PrxF_?U?qBx5KmJ_*;eY1;Kl}0jJM5qOzxq%7ALPH# z|M&lm_6_{U`v3fY*{}W|>;M1%|NH>|i~rNu1O5;HFLck~AH2LuJZ=1cCG?!Uo*^Z$+bt^7~>&+%X8zwQ5&|Lgy6;U(1SmH)TyQ~Im^oAYn? z{Xrbj%=9AY2W3Fo4n9i0|#UJNxXP^G_FK)`wP-g zmsvK9*qfB~00^?X?J@MLB)9n|WpsN?pd-@eer^13=I~7Oq|!nAvB>qyoG875lzz^tn95i<_-Ny235qnJi{mvb!3+b>lWBBw|NqPn1;{qtc z`8rp(=Di%eFR&Cc)#b%6641@1LUeU+pHZooz*^9IrE1A08cta6tWd{-P&6WyGTG}f zC2QGmkvkxeDuPdg6rwST;P3R45XTvaem3{#z8D@fQ7Mw`a;|LF8#0!EUGKFw->;S7 zK;|EXC$%4Mx5jO>6sCJWid2?Rc+8tXG10(`&D@}!FRekI4lN`tA7r^-v4y&u>XoF} zue8IYWY;Tgl`ij#X$kZMI9^M<%6%ux+ZB0b)GvUW&s1~=PDnQF;MnC-5z3ZM$PHVu zk64%1Zj)T$DY3!cHyPx6Tj|%Svp4uz{sZJ=jk*~7?}i03S%<>zA?uwiDhkM4Y%vA% z*o-N`xVeyjUQ4~VdD`U67SNAb@NO1DjMIfWo=9*&l|kx#YMQMm50iFHMLjnTCUnhf zxL6Dr47@S#7_`iLj2 zpZOG$;1U_l&q&q3rS1!Wcl5+ETrfh>T%U*@uKSc`icWmDO}~mGBFz5t5>3=@-5Y$5 z?+`rGI4r1@K(jiqn?MNM=3^CbD>!O1{lglT2NsUCz}1pdGwxN?QC9KlikYld7W+y; zd^4>cYSFH#dw-z_8b5xAK8@tKiSaFfP&n$&1veTcF#TzvF>)g`TB5@TdzBKiJdP8I zw!MF*{a0oCzruU=tAsBkSgGqqzL#*16T?K4o|okd!zU)z zPvrsP*5B+X1E=AIa3WzlFK0>&T|}({XG!}<^L%LyoP4=b7JEZ7YX(B>brPtd;Rz}; z_za2xK2&Z)rSO0S;vFApaAkOyX*mef^CX1UKb^VZ0p(`@>Cyh;!RCLcq+2}JOUy1+ z|6~%xDIG6=ctaMY`xtEzX>e)gqKQD79)lMJ9x{b_=Aqa9YCCJCJ4iSJQ&Z4A2aUXo zC%uZW!J@bR@bdPA@h#{5ODh7CY+Uoir`|U8Gh#_#**5gxYn6fhXuwJlinU!;aTs4h za8Z>TuP1eo0w;dKvRd^TEd3~+{>`tJ-Mo`IEG?cgN6)fE%D%A9|zeHC__gjFEzqtX`d)C>W|*6l4ViT5J}G678_;>8%km&`cD+_fNVGW7CLa6x3TWf) z8Jy~(Co2PbPvMT65f3O%iT@+%KU?~03c+()=xRj!MA_?37ScxcPX|f}5tr#>=3kji!gh|15CHT`V#ZzqcyU7*|G<&dyO8#r>#TnoFpIgXfLK${56$+I+ zWuU_}?~=a>T)vM!PMU3e#htku;6q|)4u0%qVm&t>mScUTc7@LW{h&)4T{Z+9U<3~3 zE-e#<$n>W3cK*zJf2?}}g}qUgl?9lkL&*XBzpO*JfU~}|6DCR>-Q8J*?jKT!JtUj* zy0^>~h9XO>FZZtPG|AXFOUhSo6n{_nG8*!9JG}3#{qpFuAE-|Dsu~rBO)i3Y@EC)lLZZ=U<_YtmN-0dIwwW4j(KKl4;yy={ey zM=poS05iH6!+h3&neKL2T&YgfD;6mI+!Mx|K?&!c^El+dCkQh`fmg$SdW`K_E1+_a zTB^C;#>;`yfS}@wG+jjaiH;rglH2CmO&^JX3Fg^$hT9x&)t%8=m8WHd?r>N^P1r)f zsKZ{%CQ!{kWJIZ25yk)LA*pNFNkqB71k@5-fm*Vr_=kmTtkvu@Q}XOBMDGOitEcQy zyt2^CVX%f#lxyI7sJa)wm3B8s#ke*Nypx5eqhsCfL1j{trkx9+|HZf`_^P8sYy)G3 z*IwQLerWLZR>L7k?tUH(;0Cf8#TGJV%5@|!p;7m&ycl@0t1GFjOhy6?Z30}LQ*LPgtqGa#(cXZS?#@d) zF819t*d1)bL}hScD&%}MQ6l1=BzGfOy^1qZpHwDNT{p)6;wHd z+){sr?9UD=F~;zP@^Be`D9Ta!X%RQ6A-F5(b`n~Z{?5?gQbjn@BWp`1?O91yo`qS{ zdRnD-LbYC9-&?NypwKBF11;2%=^%S%;zOMHVw4dk8rQNwShrRKct5bm9b@NSZ!2r?{?|so+*)>3Syg|P>C(?!JEsit zFF7*AEKg2&0undI!7C7O2m=KM{E|*5Dk=E3JCz-#>Z>U?DYhxfQ&u~E92r=6lg_E z-YZLhHz9a|XZ!%L#kb<7E9KFiG&I(1AJtEm*(6u%A83Jiiy1QTGJe6zy9`Mta#28r zabUx8fFW62e0q$uj9zdaDz=D@l|HDHJf!*$1v!V1^)Qn`SeOFP*}W9oc+Ec1gwxyqGhU0*LEG6vIZ*yL2-`9i<|d<)*W$@H|> zyx)^_6vjYozH?zhz+Cf$oj;iW!AQJ$nSFt)HMC`f&ayH!H{%#vse_V|oGiIG%$g2K zH^u-YG!L@S_R`BDif@rKv11&gZ3_7s1RnND9 zAr^K&*DSLJ?hzO@K3jYi#Ip^-0qegrp2EXQCj02EKGiTo7TNU>U=q?uwTUzoxFDa| zRH-h)coX8YIGmBBq)qZb1wWux;%_3a5+u!moON;&Yg>8#s|ZX|dk;&bi87u+Z`?4# zCY^jeAput7%Q<5nl-^ze->}-2+AMXLlSCPW(>AT~G|ZA!aVIY*iW3`Tc)O$Ij)IZ0 zRQiaNkuI(c_4YuD<)@FEX8|He2`um2ZmBmeqi*>eL@>zmF#+^mbjxm&=uSB@qH7@WV zf~L2dKS!h@;OEleECcAkcuQ5-Hz=R*8xXBn>p9{I!Z&lR%BwR!;qA!U|?hoW~1^SgU-dt{z)29=p|oP!kt2vEac*l` z_32I+ET!v0cY3JE#v4+bentrO?-X{T?KG$IuNp>unBWQIU1%|F(C6bLA4fTn!3+EJ zK`S~MMIr*nz?E_|h|_}eSmws%4nA@lHHcK)<_CX0GnXuy*aBHcY@hEf-S4*XZ}bZy z_R*42#E7yTK*y>?mt!xWO_5<>v+NX%ieB#cgUs0RWX1P6RJpEd1w^soOzaCQra9#1NQC@n*S1M1%`j8bkQjNd1GXlM8C94e0 z_ei`CAV+s|Cn)>~8n@{SAx?P<7~ziDj${#fNu;rxpSML_YIlhWqC9#$qWw zV1uf7$Qwm|pn48IgpQH$BD~%zdBAEE2T29z2aNBsWZXb@SNx%S8xy4nyc17My%e6&c8HhWSX<^UbaZEue)22 zm8JlsvX0gAjtL3GBz)~$!PxIIlVDSu4iz4am^Y7>M;ZBvJ;iPA>HAqhR$ts8fI}@+ zmrbw2Ss&dKPt$Y5u6$rp;Ca|2Ch)7dKHr6ogA4MZ&;fv z*0H}x=>m`yqWP@(+^TpCe^ZJ~y$4!HBH7eax-`7-(f7wcXx*XO2$kUg$Rp6|?xJkU zrn%?ZfxmsTjGDet1&oh^D5{}HU{DC#v!SzE0yt<@mwq4}O zj*YG!FaE^zu46Zl?-#m;rp}x=O?S!hmUa)-56PpgaH<&`nh3MRw!-kk&` zj1q9?)dLH)TZ~8TB4y)JSyLEnK>^$+kX9tsw5ys8arx>Uhvig$bN1`JWb)5QxX>zK zhJg`x{0(*`xrpk!@h5y0_*}1K%Sp7L-0`T!L55c!7S?d+_?Zc7(LY0jq)6IQo2?5 zE)B{3;H%e_VTjPB7`(7Viy%pOgX?}^YL2Ywb7pt2#f}TAX`3F(k6JP++oE{=fh<7HsCXzyOKPltDl0 z=RvG5=boslAu)xDP8kN^5;g89RNlZ(oa)z6as-g6QRB# zD4#Q!$G;BWDdi6tu$MczE@+@nUD)ehoajFb)cJp->;y&cHRs&M`L5bPN!Iet-=KG3 zeRVil>_>5;#;T3l++Hoqw7!yo2AFHl-tCXWo6b@S0$DXGAZvj@l+3{qz77j&VLPwU z&Qhm~UZw8haG_)n1`=PWxmA;RaRe*q2+N{7J3l6G3GTDZFAAJH0A7msd{$MlUYA<; zpRv5KgkQiWVoZM;_G1#2a7!5XxiHlgB4Up0h>022CoUxNN#9c~q1J)hDRvjykm=+| z8fWiFhPB_$-KjA-M_HRp%pB6hOk{26BeyjDtY(Qs5 zKCk%_O1R-(?D;!*BlN5(7!C%-^xMhYP?_SC1xNU3P$PZ;$&sJE`SgP&Y0rjN8KHpDP77(AJX#LW}E7KVw zFxAlt!T_F1(S9RJC zpZ926-2bO5t!4oR-efCZd7&|Q{^Osf3Th(h{2%7&8&-8r^*_&J)0+_mdrADpM7(&y z;+E^x7lC5*EHFId-J{9}@C#E9-3h$QOe{Vn8@^@EPKftajp6zaG?S*_z34(%A+`m@ zAB-)LK{nhq-m06@*PkwdXGQaX7&wVM9&RkudhChO2Qi=w?lP zF2rz!FZAxk-bqf6%ax_Z7S)tD2m^5pRePu;bajPR9>66_$Wvmo)OG!uIS$nsyn`81 zCT7VQ^_}*xMNFjsLA%xWuRqZcp63^ScGVnG+JLY;%K$BpuI?=#`q;Z3qdWCH)FwioJ-esi@AsQQ)7OXQaD~c%J7b@U(@CMm&+G{teZMnVnQyG#pmw znZ(%8t9t@nS#@9r!J17twTM6)%{`oQ4rN4tf4WTAnZD%f(js|wXcZd$WOlqIfwVx9 zsN2LVDJT%BM z);}VxLqlkOkm5{7p7rsfwVNDpVtE|&zz-=S`Yj*>VVu>ZsEs(t({uX}rMDwPyfqDR zlE-Cn@zbdp_94rE6}($4+BJp&6nSAN|7;@;>R5p2qmW1NH)isdwk9cb(BmbZ{-H3{ zQ$@i$QDXw8I?xW`({`3;luzO*!}69X=KT3M&R`kNbT{%)7cR0QbhN4mxeVY>zc+v= z+a#;FgyK4>wR?!n(6IF${6+;4y$MWINWdy-x7rA?$~Yx#N)5-?W~#9nno|Gs)jHB#GFDR& z1drpoKIOc{Occk!`Kos|;eqkALeL-y=6ZCgY`1N+jhTfWgBpH$Eo;I5Yq%S^aQXEs zBDM3=?-Ag1E=NP(XH<&ruPwkZmapMB_^SdWS8Y4`vVABy#de$QW zW`a{jZCkb?5HeF}Fh+sKm`dn>I?{qOt{BP(S5R>Ic>C|uuB=y zTm2Y-ogoQ}*ocq~pvBW=x+Iku`2$g>3;V3V)g76?1C!*|C4P4&#=d!=S}XJs+C+;0 zyW0}l4VyI`EGKPf{eBkYK}hnKF6}qM08AbJ=OEnVtH%Zs$d?5iN6y-TVv5I2>f=qc za{RPkTmJpwL+|qNB85PFqD}Q1sny9 zk}CpyDGpD4horXq3d+nzTLKU%BX4#4?I~m%_i(adz|J% z4lla=%?;?ku#S9s`cL-N-S5khClgEN)*?`uwcd+M@jr}Q9UPE)Z!@p? zhG}RhgM2po#)5~1tP#UyTGP*uSIT2Ek@t8#-i3!r=RC947vVKVsjt)DT$78MVoFeO zmv*#WPwJ@#1al~_rVXNBvO4^Y@;VW(q|*<_b2({tPp5DF8Y@q)sx&>A8aA7_D#P49 zQY7~~vRTFw*2K;`tsbR^*}F5S@@eU}zYUXm4JN#F3cc6mkr6$Mm$XY+@)WGVxFM%R z7;CQ(y1h%}>{^+k5SU^NZAuYT%2uMO&bZ{O-+VLsNL*L!oam*7OD>^VoW=6SocW0N z%rRemEx%Sjfa~NEV8FZcjw$Dx@|9=!Tb0~9G zv^OGLxX7PyD*17b+XHZbFD9thIT&F6wub|DeKz_~TLZ2|h(_iSGO|AJipax%da!-l z7L!GH=eH!y$;DDlh5-B%3delg#;W@7!C$Dd?G2n6OhidyY2t-d z{(W7M+=mJhcRvxJKDHu|(&LuawB!p)MPB?>xsut!q9DFp0{wj{b=o2%-T)}V@=?SafseJ!j677Fw94WJ?oVrI00W%$VW&)2^%ZaxSvip3`SFe1Cl4{AcCGV zBpj367<^7GX-`yv>0PPnJ5Q+$Xa#mhD89NvNzMM(W-SiT;O!oHT0aL%4|2}FYFVaN zB6%E+kH$1?zu$gC=%gsz+V7S|YrZ(QqBx9H;oHnW+Z-7`Y|QZ!K+=Kj$CTBwS8e}V zdv=K141E}kmz|a@Qc&AL{U+C*^%;znf~1SkkI>ltfT^eG3>}r#4+*`_saB4Ami8%S z$7nGOGsGJeL`p{K^<3fgvRIu+_Y-%h3;=E^geJNA+fe`Us~Ls=;=dqo?he&GQ7f2V zy{?u1+^}w;>qFor?6_Zvf7jz%_zsj=AU~h90(tJa2wCY4ChDm9<0)NN-0e*>aB zMFn6-z3e1GT8oqJlpH>8=Rl5sU~4FQbNO5?NBFe9l?vV6o+f@GXDCD*-F5hiQtt`A zmbL3R6S05u>Kj?E#o;A%nbyC_+qm(ERxyLXkT9H@;E^as^@gaID`11Z2jUiy9e*IUX1pT+#e_;sed`J8_G1 zy*junoDS}5e8O%=Br5$41w(t}$Y{3q4BsDKih>5*pv{CUYv^azedZxKTL`r00dDr5 z5@slfNF`mqxmKnNn8enM7D(4MqstA=(2B~Sgcea8pM^{;(dqj$AGn%`QK4`_CXl$_ z$g}oH*EKz298F0WKN+hKU}Y5^Wer8bt0cTK8dyCG>C;y?5cPXExjsJ43JKgl?RrAM zYEM47!Lq>6d<(#BXT5CMbv#!Mt-wpkvkrpw0GW2I;!#j1gGD7o*Ln5-(5K7VpR$Zf zG)d8+!z9-ykk=9|ZF=xZh5}DeWRvJ(mj7p`MG{5$D)vqefalzs#2o}KXPgH(T}L~W zSCH2|GBAO;!O8!xv$H)Z08Fgs(7n9E+II((eV_fE29vu*KiTWRw?y$H z5=XD#6+)91?^_qT8We(7fzpQ{}G z&}bCK4q%C^g^{VK5ZoL^^sLXEYF>+B?z5~>aF;o9v%?JnLTMuRL%PiF%1Z{w?LXn! zTp_+961YnT&8haxjMJK08_!;L3&DYws2-{rrN=|)jg{m=`3_F_KeCKY?}|fLs{6Ov zZPS=hBDKZd0>os}2uGRedX}2r2W0C~i=WQ_OQ7&Wavw`2X3z5W zfa71QN2{?hgE0brl|Ege-M`Oi3KRF>)gVS}>pyDGxwG9vsUPfNont#2TROBVRA$nD{7Ix_i@l$@LpDZp}bExiNB!Af%+ zWk4epRDz#$-7dfkkgu9-vG>+M!fqw)$4H~YBllUx5_#-LFkP6$FNzK`CRjJ!a~;}$ z)v98_`X@4OC?9_joI;E@GkPpqsDA2F@wv45>-*$H5Z)!P-9XMuVt&w7yG58xPkv~8 z)rgU1!xq_l2brcRQPtCBzq3p!*MbJt8K|L1wMjdvMVGB8tekI<4)wu<2w+AJgqMs; zY2K}CL%Y@M!N}~Jyv|!NHQl~8$wnq?Zht;}Z9&~H-A@OFThi*7jyv6Xu43V>+xC#n zi%aI0ttnN-(RiCIAhayE1!Ub5C?Oz}EpJtdBxAdxf{C3@OIOK_;-IDlGh_&s{WN?$ z^tTd{yPp|&;YuFlHX|rh6qiwKibjDbDY>wa0ih?EKWj%Y^A1Xs(ade#S8C9pSnXOo zZ*|^`$iUN-5X&^js$yS{2>`HW$4t^rOMCxCyG#0GlU4tOl@E6D@Wz7lcF_txPxqrp z=T!FlWK|uNDQQj(6r`?-6&fcBFG8ke7E9zZ1diM4Hk-szuWpitW0Ddl;+yXUTa&Po zq;6LY9p8bw@zJQ_Af*a~W$?~8);`={6I#@3w&I#fcv$Y0!@fuq%@WkQX6!H#y6qIS3)@CalFo~zPT(a5kBvK3WyzCb4-Bk8mY0dApR^Io%x zRntr`ApewM=i-c$(JV**iURCD6eVF@Qw?|dGsiOP#rQ1lcvoXD%2%WXR%KA#u z+e%_b#cXAPLCKFXbE3#D}6%RL=~@Kmbr2pG@@8|R_~g97WAGp-1{q@Ew1gLVY? zXRN>bwEyTMDOVCrFNbtNML#6|kJ7~CB_v5khg$RSRBXxUTc+RVz7J(athh3OX!2Da zCg=j>$wq)NfqZmHK?l;4)H?F^C|39hUbnG0& zL_mjx@Qw~BE{}1g-kzX#hL!-S=j(u1kG%?PG4?BV8S`X<=wVvXL?*Lp^j2lqfF1&l z*|}G1p#VwjtV$ghmE+ka7vZcM96ZDsy zu?bW{DdcO@2|jNZi_k*CyJ$cUA^%roA`$!82oePC27S15_XATTbMXT~Sjr@e>cG@hd9h0P z)fYqY4B;y@BWxfzCjh7-!FqWYqmVUv=XI?fPBm$^ZP|exC zh~%OJ%7n78QMe7xTY=2geQ(PFi0tr{46yRiKYQXp05!j$DrEsl+VC;MVUv z+9ku1X7H?(E`ClX2Erz`--++$4Ks}xA) zB}<6Q+ISGr#F$Caj$84PNHYF|vkvp5?W%yqGL_Y7UYyM@A1f6VZ zGCZkG8sZ#UIVJfHBMOJB8Ho>bW(&&bAwJOho30qS@}BBeXTjm8#uJ%Kyy*3#1ViJN z?jQPmzSYGvw>6JvIuL=YMBmo-V%33E)Z8^x|7KuTL@>Z*#c|Y<6dZWmn#j&%07hei zm>g~N=85PFRMU9;jsmNF(GWT~m>z>v9|ABlQ-VMW^i^fR0*foJU!(T6IJ~u@Ts%j} zY^)T;-<3l4VwxkvX*?$s*M)BeONCKcdn!{jE>0`+QI%0$Q|R>QF$^k3eOvi99+aU6 z_&k+oGm|O-Ms|ka)%%*`JZVmPz^p9mh|R|KC8&mZ$=f}U-r!nYCXpW*8=>*uq39 zG1F~{0L`}ldA`*e?q%B+-k5g(y}M%ITCHPXG4_vjWT{9)EaVYz@+eylbzumZ&xp#L zVAarwrC8CK22~a%gdTDz#J4EJ*c9&n)*81|#hJ9D5J zCY3?`cfq$U7Hv5a4vJ zd_dgm(0JavhnwD_0`O2Hy4C?1l$F@O2ozyrk>^M8erEbFM(@l2Xio$M-1$PUIv_DW+8Ols{CL?G-~AIezk{x;ys z`jF7$lW==5DQ@IEZElUjtESb*6j++e2tT^PZ|sn0>XWN5(w|tjy9?dAnIvZ`WN7DVH(ZEkaCj()ZDdRwx&s(sO0jVXtusQrwR6r#vn^&Bg?<=`L9xO0lT%1n_c?)sZNUxqN`0;OlCryKI z=$ELOn+>T6U^557o_`s~coE$PQHO$X(Ns-i6b;niKhKnfs?f<&Un zDn3!3L%FD+X(@5}%sKFfRX*DpW2tC#0528Af(IG(a-HL6_X4PlgDpO$53k-jrY>5~ z)!TkgL@HfA`qX;VdcYDLq5ID>SXLn1zM0QEj#sS4RW6pmLV}g7x*;Oj>M)SEM4MbR zihno)-=uUKA(`c09j6is`0xET)s_(`r16tK8@1LL%(?1N*GDeG$nY(LBs^U}OmkV( zgC7Wj-JJ2#zz4=!IOG}nXo)VEmwjf%5oo$#-P%d4kh~PuCSzr?U={V`opYYG$r+!lerrwzuu$pX^6^As#t!c+KR5` zN7NjNqSHBjZ(|3S(8k69N|)Xocza{NDglKac?_|I1*|~j+r*=zIg8F`P55X}n=u<9 zdS}Bl8k_Pb)&~-ifsj=@Eh8SR9!v(lv6`OEr+6S%>)8+*(a5<#JAtS-zb%IADmCWd zj6N#W6H?03Q=Vy*-;A2rLA-4md&{?CTpa+^X_;0FRat!AujdskLm3XD7a2dJ)~)kg zak)=Zz%Ndp-a1VXnmge5^#~j;kkMK9hy#v$h$0juhbKETBh`buu5wg?OvAY-rY6eF zI>oFNj2znUft1|Cq(>~HbkOtEehWKWj%YqrkqY*l9Hz$6^aMMlbpYq_I46R|x+*1`?5fQC`_E(cdn z2d?FHMFB~r8*c6}!%+4ObY38JjS~K^JL`jB&g7W&G}A%PS*IqvQM=a^0lK2H^Z;ew z|GK;0w%f*e269zXo+ywgAyV=E@=#(4EdwhKNuFoUOVRW2B`jS0r%O$OFM%ifT#S<{ zHa}x<4H0xSP*^9*GrG$K_(p;sq0EzqqVad7|Nb}EnWlMjvXlx?YUsNCjWlvHt?L8UW`H3N}Fi3c&dh`D0 z`{%V)oujSj*FD2tra_Wr&O7o5*}-P)iE~6V#l?ibxDc4XaI3JGe;-;8h8}t6$w<5I zn>j~6<3wp=Qo^mlcPa7Qs^q~uz7GHe5I-`qG?zfid7sS!NUuMSYtU(;_!~{!wOi=M z)^o2L%{;}mA;o|+QaDQ(a>J#oRhJdsp1o3m+^eOWXN}R4GKB?u4{_jl%(HxsCGgJ8 z7NdfnlGvp46sKHX9>lGs=U-EUaE|f&-AetA%+HD75(n#e6{4gdzpxy|5@`Ds;xm->81L z-v`?2Bn2q)fR41MmUsPAH3QU+IQH)Iq!ai zPC3n!;S(e@kn6~iz6b!*(FKcpfeN`gkc?1rBHHb@Nrk@|Y1^?8mpz+%sm0~My~`ucV^VzAQ;>{jw}UJ{0C3bn zker7V69mR(`^^ePkO$|7HNTt4@wX@}Ct(700E7U{u_gY68-)%KH zWQE~-cgR`RriZQys#$z6>ADO6XLe;{xs+iaz6h6#=z{Eb$;W&I^R@K(rKvy_YC2Fk zNT#d1Ui1wFzc(tV`AsS)Pm$m{%TMriUWVq%4Y~>4$F{9%MK>+sWf+Arr)s7vN=u6~ zS)6or7-}5^QSd#7$R{TYl80KM^WUpQb)uMUWcO97Kz4zGr4;G* zkCLdFHmrC_uT8y!rc&u>ev#Qct=2y%@KK6spvWT6x$=3VwcRL1vZh3JMdKAA+2*78 zy(1%&&>YuXXJk3m=)mZ8XAWIsO0{1Rv22cg z!Md(h#PuvazfD2y<4?aCex4VObo@9}fKT!R8(2xHb&uoD4t}F?-zio!YA|h>AbXkW zDH%<=bg)G8lO-0q2%bSN94$y9P!ZFm4Lsr{>S-2P(mwAwP0x721 z@uQ$eYHtUMyz?%+eip}=1;1oUI-a7kO;cc@*qq3k_BoU+2T*{MK!Ny?R+}e}3mW71 zUspc>VL6wE)Q-!)Q}Dhszo;e7fd>7+rau z#&>JurZjLF>PiN(PkwcYk^9|HVvo`ad3rD!m?T2JhukIblR{jHL^V+kTa7&i#@~_E zVR?Z4%RwZiP>Xu4HU)W>-)Z*hIjcr7=+rSoJQ z>ZJrYHk}z(A$!9fVBpouPtkp}g(Nj%zNWpy8zlE)S|cp9gEJ ztK4pEycR`eH$9IFSH&~)9tDga!NT~RS-2PW?(tq_B?;LU$Y%4$`{- z)}y^MAP5Tm7WI`(g$$l;sOn(Xhh4Sauh!=NqCa&-72{8d-0xH3B1l=Ppy=(rR>T_ik#I5q1orI2-_U z`m2#m-CfgtI-6^|CCY$Hc?~V+N^t>Qp~}z8G`FU1m5 z>|Y6T2`|3P{ua3uM#(i~ezB0r$==RE0nus_jFg?@PL}6Y)jj?RIK3#k`zG)L$o4g2 zW#8~*_1(*aUrZ(!j10mH$m=)I>?T7!rjuUa@2^R2mp93L>2X@YxK09JmH?G`KX2zncQC(|IteO+Yl zEJ4ppS%$rDDPjY#8d50L8)AtT#q|BH@IO`R1!nEVTKHG znqszoww#m638CB#li$4=296C*ioH?%MUaItcksy=f^7t1873F)(GH}+r$r8+q-o~` zEa}Xf6Mgz{ri_lTL5kuQS!1=tjMy?GqXlLuKB)mRd&VA(vb157&Fc2TmK4(W!lL|7 z1#<&X%^x*|hsMf~2xyGl3_nU;FmOlLv|sYK7M5g+r+t!io03~|$LT;%+PjU@mh3%t z!tmMe7WIdNi2bZv`1!(y%QI!*52d*v!r4&^4r1SO-0bt7&A;5O6)%$#rS)j=9_jucbbJh6Qm|nzBpFw6< z_Mjr|Gw;}m#mUU_f{Bq3dJz0nA=E3&8qrHq%;#v&-Amlq{$U;Zc-MgU{K?Ou=3S(3F? zp}El3zsn35(-4%_T)OIbvE?EEoz^2uenJan)R5E@!A8M0ZU8et%)beLCun_g02h!F z?%h$ygu}GhP(ZgV81MI|Fd!g|pnoYmcc?m*EYyc%<+5>Sv#b+Uz(|*A`M-i_D z(7;|J5fhtrMXYO1ANoKRW^tM%kiyOktwcy$?dDmiGvjp{0G2Go&`IzLJ`p?>L=%=) zChj+4UT%(Wzc@@ELD^;v!`Sqf{zbkS`@vQ!&HiU!Z8UoUFpPMq@>`%au!eM z)S)SCaeSNACRYg!OG7DN7T%1pDw+uN4Ld7p`zV$aM7GcTq1T9&Q}XvIIDJxKx;Ukd zcQ>IDTIJnYBcZ?QrFD32L>>17?`0NG35Y($A zt^^Zg9$!;BuB>sBEd$XkR7A53&nP0hCD%oz6LCmXih_kJUL(nfnqsB}5u@g*-?PdJ z^cq}^8Xxndeb2M&%5re7wFsv^GZ&Op%g6zenKP20A9x9hHbq#b>~XGU}SS7924kztFq)}opHnz58=eA zgB0y+Rd-q$5B4o7isq3f#jYlZbxBwk7LFDIQ?Tt26iKfURd0}2RYD{d#R8N_4xsJs z+93vxhDl0*(h0bjrLX}+1Obuas#nu{{&$Kh`rZV)gG`eyH1s-^!t2_%)5iCvUCWWP z{E71Bv-I$qGF>)z3Y~AS2bL&7qAS1~DPI7&VRtFr;bVkLW1k(turUD9?_cB9@ zcc2|GujB}#NaLzLO_-^NyU@NByM`Mp%A6N%8E6KgEJd))6^8%32`nAfJcEcwuFB~M z8+H35pey45&392PYw&RH@I#uB4Xc`x%YCWsxpdg$4c>NzgctjL>!=@Oe=uLD+VssFs%AF0?O0T~rtZ#}=T+=VkM|Rp3Fh%-RjV*Z z{)D~SU}r)Aynf{_1gyia*4M;7i?Xv9GlX}AC$0M4c2J&-+UFH#x1u)o2b%hClXu*K zer7Fy!N#XhZxRYo0d&u1^DvNS>$I5dl#!s9ota%?OospkR%I>*i_OAes)MtaU5vZW zg}))iO-c!t+_bfRXDe%s4>f#F-*ZL^g<<4)56n~6B={E-PrwOp@Vay|^>`rsRieGB zCGV{+Y?N(|>L7%Bw2LTOxOQ0CP$BNT19`SFA(3A8ZmOrIl-ej$YjX(EOYeoU5{WU$ zC5GkQHYz82vfBXoMPpklSq#WMD|SId+gGd(yFH ziScIRC~S&hrF6wx={SpJuht6-U1HjEK$G-w)C z?e65S(&VSkQbYe2lgWDhfHidQ%jk}y71Yocls9boDA^?^&=sYQc~>mXLLE~!~W#kmq`CpFc{ zyD*q)a?s)Rt!H}%nU?TO7r4tZEqfdLyfAg(AcJl&%dWkZ&@-wYP71Sspw6a44gic8 z9a~tvt^GS8(xs2Aqx+G~fb+R8kpBdvY~c? zkN}>cKC4In(#Dgx&@GMcr%Gm84*Wx0WjmrW<7zaRpBW;M(QNC@%JHHRCt>V^Bu?G= zI@GQGZ@Z}oEDvt$58s2_od>w!11k++g7E%_Z#Leqc5zh#zyCL={LySP5HYL@w*z&O`c3Q1Fz;jIFl z>g9fWj`z^2u?*?hy${IMVMW|NwB(iIlvU&XgAqw}*=;7ftCXEb1rNBT1rL1WTeXe= zntnqT;lV4ZY^?Sq!3NQ~giTgk-M7dwTprY(KJK2mzTC@fDJfwcy#Nn>e39sJ@R&my z$B(7s#$bOhPAU5ypH@#xZ%|;jbw`1kqn7ejnXJ?+V;?dhLC9 zX~{$``|0XR^NlOhsLU}}?Hy^ull!K?dS_lH&vkJ&!G1&7Xn1}I6~R4UfxEi+*fu|( zfk=;<(Q=B(8yFL1`D9-JlNCQaT|!@U(-tyGV|b7dr<7x>?ocZUKP9XyCD!+@@)O&F zg)xj%zB~g?nn%{$bk6tN3!}l0n?k)%enMBWEXTNGA3J*kmQ3uMlM>tsAE$RFjEg_g zS#$ES?Hk~~BB5a)*7y>$5gFQbt6?$;n9zJT&}(-&PzqXv{o7y@NMe_HAOjLdN7Bv(6tAyF5e;X5C4PcJV zh3voWoKzL0MNL^UFaPFHv$DpFXkJ_i7m(F3EKHaY40ymVT_-|T^LPYn%e`V>j_{{g z$YYd(w=GY^AsYWq0K2YI#utzq5JH~XWnYc$KknM{Nr12@%rgnc0_%0kR-&5^;t9L6 zMVjk5pvbl+o`tr+mK`mVud5>v^O2#PxN_zV(~neR>MlYUQdK(XK+3S?P;%!lwRuq( z6ragaikoqo5p~vaVA!*);_{e`j{Jd1gC4+a7a`Jr>rzCyGg32Si*BQJN|q=QGfLZ@ z@$wrAc;-1DvKMA==9QySQVkma{!lKYA{u8?Tv|ZG%Jj3P2Ze78w4zg}#Z;EPHKohn zvO}lQ9BfcpG5Mwi{9ctzD=%n)+gc7%I5+bxN=6sT9IOC;&%w)_1Ax|V^Ng&|%k@Y! z!-u$);fu85D$p|#*nK=xi5$*L-&~&KgrO)&u14#y#B;51cCsUhjkG)Npr|Pcx>ja3 z;nLxhE@{h?W~NDb!TtFAyE{}Kf6y}}$GNb2I~;)B&lxuIDl-Jf`)w6iVq8v&#OMYS zDEZ6xaVb2gu8Q^DxsxVir+~dQG*X`YxKs!V?+qTr1Pwe$h?#&RtXcHj*f5Jt(HfTB zE6I~~LD#{EDaf;l77s_)QN~B4Pd)pOhk!e=ZA5aEqNi&1fZK~Eo2W#XM&!84vOXo+ zRCmOy_Tm3cU^wDS8%wL5@1K}T_0VH@0^U+Ra=)~@HUzuPGL$e%q3|NiY1J1 z)_oF?Cl}9l3NH|nv-=ZwTW&)a0Z|&4HSz8lpFNu5!#>>e^KrRzQZaiH5U8%2Qnz-` zI}Z$sDE)6EsV`27MZ~y%yIJSszL@=}6=uji57g1u5{^Gs8=@bo9M)`z^Bs@P0k7C8 zaYwK^kC5ocF*1K@4w;GO>OALz+d0k%TQm415x5a9ld5auwwevv)v^bA{A*^W?@MT` z2>w3de7K)n(`kj!YLR(wMxup6GPnLznB-@?se0=*6_=ovU<~I4l$4;)V0!u-)Y!0$ zk2=B=1lD!aS0KMwkX9Wc>5IL!%T+bH;n&^hy5MwWGpxx=n}1p=T&PL*_JOyT6G_u? zv>AmBJpK}2<{dB)esrScw^Kob8kWX#1FwEsAge!XdHXng7K}e50cZI^1pUcZiHr4i z-BY*16UVAtKbCiJ*7wU)pmG*~e2W`DYK446;#kScM>y^Jm(P<@gKKI+H8++mbCj&{ z%WuU!*SzUMuSDig$mfpWi^X~y;m-^pA+f}oSQ=f5es%hwhv-XMct3IYH+Ni{b{=@g z5TzDL( z3fESEBWrQFL}d@7qARngBKgBU^H;{D)M<`TM*9JC3RkA5q|pJiPIbzlX;R52#}=IJ z(&#>jF-~_r_~;y?KC?)tY6bXy0hgp>zxr;yBfBLTs#e;#(5I8<}g8 z6uKNz?6qtTjCfy&>Ye4PZi^2dz-hQk(TmoAV(`kM{4|TJ$#<0=Yc+_N+#uIRyvU0bfrFtTyqJOC9T$B@ zH0wrk7seS30a+=>wgHnYeO;I)g{b|PEjYn8exkuuIW@~><1NH1k+)YA*86Dr8A0+? z@b@(c+^JuZSkVi5?SrU~vpTyO_*qVTF|u3QzK;5H^#Qz>lIke?545kGmXvhRIEV>g zKg|&2?Ge0VHaCdj-m$hY@!w|;kEHCVso>PRD6U!v@Ii-dF&-h_Bif9o9MQ9NUiJQG zBFmEG7E3W};#T!B!?H7j9@DD-gfya4*tW3C0Fl-Ei&OV9w%EG7ejw4X??2o$rqcDo z!Nx@UDuNB2$7A6CMJNMCeAM-I(Z%|tz@t}$zgt9=W1?{H^9K&g*5=XQA7_cJq!8|CS9 z)>=M2i_XH)%Zz_s`yNzr2v}dv3#ATAVyg#f)dft+6MxBSJ6Du9r??3}yMxGWkgoxP zLjdHGmqQQA8-|w9sG$^*^C|OcXw;pTX-O)j)r6yqTg>Jlq!5ozP8We2UbTc7@>1=8 zQ0}KuQjK-_k5_5%%8*niE+B~i%XTY@dJ4D{!yK%H+DLRk`D87|Cl);gl8nOoM24{t zXOSLr<$LvGE9wMbGCqCj!j{hJXzaAEWyW2#UG6-PR^ceF%MG9xdXfgY~yH20|{_FJtI;5dXx*U^RZ{fVc^Q|p;QYWtR;w3`L@(*@Q0 z7e)wN!g*V=GKkvqkk7lvP52jGo?>#E;oq3Z7X&Ay0u(ZyHUm@AHaLI}Xk3BM*N|7G z#IjQJALb?B)zo;YwBHH&CA2Xp8;QY=9c0gq4Cxzk07VI>YSjLg>dpX=K@;d8Mr| z?kTp~?Q97DGkmD(PM(Ia9ksaQWmH>``8_v5fNM9pt~Ajk2G8A!Qd#(20eRG1qFZMi z{vxMNh!E3OZ5CxX$a0?|0aTjq`IZ2HDm0hj;p!CI?O=UWU#e>sT{GVr>zFr+qKeUs zz93KlMGJ^5@&CE9CWRGc41cAPGn%Rp%PQ^>2Hkesp|dBH2@5+C+W(KMk;Ywzrwntr z2izBb?NOWC3LDN#EgC|a2*D!^(mw!AfJcq6BKNOv5!=C=zaDT{w`cS_o7`~@zgfjZ zAneWrS>gJ){RNI6&u1~See-PNb_&YXUpjV!1^4)trFN8(v2*V)75v7e+g9!2~u8 zd!8|>z9*;bs?h38Ue8g>jAs!)7?-Yu@Q@_t!v^He>JM>g(WD#;ZiInPkv85;nD!=9Z^;H;d3U6^zF#%aUV_c`qz3iwOMquRuf_tirOftB*9$mpZgs7F`W zWUc_MN&N=Q6;JqIGJP ztpbN_^cf5}eErd|7pCU1mqxgLnc-5uenyo|45^2cRBF-uZnzySnGMb@$_95nK^nev zal7g_);ptyCzLfGM1EN3hniaK{HsFSZphdYy(IG1nilgVBM9-aEmQ8cP<@PN>4Wd4 zepQ8a9kp;}Xqlmb6os}`*^Yr(;RN~iW2Fq50zUqj^OR=!X@cAE!1TRH`aXJ4y#Kjc zF$ez_jVKMtsQ3y(+MH~(3*aicQ|vJufh1xCP?|GkMxnV&$WNUC+?goMoC^Nh#m2-zLXdx>>1{9qjyE|EFeAHyWh00}J&{>Hqwo18AZj1Ld!<{6uL>ia4z%*KF zev7(%k_a$a=A1Q&(IO^CY8iWNU8tPej{hvhl-gzs^nO4st8}FwD1#=+3G>Sy#g?sa z66YEA|KOyt95BJ#>`O9%pz2@ISHrM4AD~YZ`Dwt4j%$**C`!iKNP*Ru3KCf+x6_Ep z1pehIj_-x*aEs?uA+v@MC;I!R@`{YD)tn-vxNM9HfZ1@GJG`a zfOEt-XEghwk5_$N)*;>MmK>;-Tk0gN+Kfd7VMwd}cs1~X8Jd@)PVf>W-J*Z#f;bF#RY8dLem-++odA({pkt%w*|!74RM=a}9`mh^sUW0# zOAP&-H};r(LZl9>SSz8`mzB@j~UeD(fLjwXJO?fGCWlj0sAhHbS$$AWn28d$P zwOvQm6G9^`&d!8{D=pb%f+0E8%fU-+)vn?QkZQv>)@fSgOF*e@RQfsN?EG2}8aenK z`s6`hR#cfq2zF%Dsr;~2Y_xnPU_`TKfW|pXx)Hu|wki1W*vE686ogJ+tMRuy5JeXB zzN`ZqGYN5}>Ax(M+mdKta}8hj?lA?-$f{bg1&MQma6B;mY)>>bD~gBFUgXuR_gG_- z+yULw0_1r9Pntp6%46Pa9%1zl#D6;lx5;6&JX2Z25$mfjeWSVy@-aO?0Gx4;=|usB z!*7_h_n-wC?DaaLXu@$i7ohaxEEB)e-4Vn;sd58Fhwr5tIovclvM18TiNdfpCy*v7 zTtWm;93V9MU4%VUnJ|;bdbXy5?{a77izVnynQzf?8q=_vd#BFD+C`U4%IEPo+77uw z52rV18VV*&Cpj~lESb}JIp;y_n(3fzoea4(rDE#bbj>{H4vx7HA)6g5_)sS)m zr^4IBL>>*)mf3m1F#n5@G(+of-R@x2>Hv~4)Lv)TGKc^xl5ITiBpm#gi9 zI8RVu;vJRS)XIFyiTRmq1~_&wrmvph1w7x9EVu`@X!;G^IUKZ@@G3gav!U-S4)EAeG++*Q zrQX}57`AeB!}t&jm%!31ZgzlS7`SJRAqJ~m1!X09Iy&$}Yd?{RHz^Tc zvm85edoL;6X6c{n;>Sgb%h0ACNfSl#(FQ_|acY5yG*i$OB8(7wh?XZyU#P9Y{Ya1H zLw2c{?~iN_0X}70DI%h+2G|ng=QgGa?nzMgUE_jF^%G1Nsy6{5kC0z7P*Td%WQim| zl1>TRTkU&f))Xu}{1GFN|1R+j7gDN3&I~v|bC8}e9FY>?u>8tR-n+kan$T0t?{VMF z{En`ZRnKgNAv=}92uc53&~oXMpX!OPy$(8veayFL}4@N!zyeAT>oE1suSZgs+;NQ-l}*KZDV=D1OciPHEolk`uev-u}+ z5Ve+)i;?d=*-9FhVc^T7B#RJ@>+(e*GF1*iK8Ea(r)Nf7X=f7@sZs`V3=w;wZTl~X zb~h<*zEXWuhd`p^j*e)Szv2%cAx%zdliCNtGneynhXzJ+gZV!XDD%$YdmUQU$v6h3 zU0WSh&o20ZBz*k=WuD%it{D&GnhZl#kgYPp5ygb2+WNJzVL&=+Q1oTU6F_;FP$s`o z!;enw*mH1gYk*g9(ZwO$+lg6qP$R8=@jjqxgkrDJ-<;i{O`>6*XuUOnCR9=6&@SBJ ze&A%JxntRdN>wJJBUT&xPZ2b@eOOV)-yB6`)10;?0}m)jshbRgCxYk>fKy(QhTh7)%3li=!8wIiaDn3f?=w69{=A-gCdA z2Mv2eD?YjFFfQeWYP)HH59a3_cpDIzEZ~B$YlDZY;(@B#-bKe)J385C*j+WW0kt>2 znOFOTwd+J?043P&H12=e)T9*o{J@-Uv?9w757x0`Xm(;D*oC)WtZa2rvX7~2L^wcG`}fxH6B8T z)V4(URB#om)lmMK<*`EGvKO2es$Tb0I;|x+Ia``O#IdOv?tU=_hy=*Jw{T?i50}FH ze8-Q%%JvrNV=KTS-@19VWC%T*G_Er)L=q^{H_FDotShQ`7T-i3qJ=gC#a;lgdC(Jy z=@R0VIMdv=_KE1#TUz0s*7D;EC&@(>1As|A(FBnd?IfU9*GbP>Yp;QPPFtU7v#0F{ z&<*x>FR%kNye;}zWoUZ~6-@VgEd3;JTB#f)^N1@{s}&P@oBcNXc0F6o%=6!@@|Vo~ zhJ&Z-+&~xNDm`8WvhAhg^)-&)!_~PKxXU`Y$;S~CD$n&t^&sR*Br#M|97?LV)cm_w zxs-ULwcgA^gae$meG~mb|8vWP0_z1xs0{VssHP_Lc56-ro0Ncsr^AOAS90^Z5(~Tu)0FO&5U?lem!M-0VBj z!Ht_N`<)jm#+rDFQT(Li&AVb14`c*EV30KbO`{ke6A0mygTYUvLoHNS#{e_s!rxIt zqf=O3)HH^=y^Yq#mIrdy^~txo0Q?4vxx!T12H?A!7(&D^0ub*&LwtfK!<5vosh#LDdVTy5xg|(75;&1*_MP}}ysn;mY0q})O$W+C=| zJ>Ou@RqK2AhZC~ao)kDj{Ovd>k>C`ive85?`WX>M2dPc8g{Mq_5T+2qvMhu!D$$HU zIEK>sqkn%(23t&}JfW+mDQZ`UCzPfba*tvhN_dX^+j2F^_)(u^)Fg zX?!6jcbXV22H0a^LK6l?O&^kaBX*we_G3~6flbb;RrZX`P;DT76j{NM=Jn@p1td}p zbP)remwj!2u*qaxyWfzu(TW3&%++v(iCpdZ)dR&vZf3}gN@aH6LshHo0;zKVLXLd& z&?S47t8FTHob!Dt<|j2GH>2|{p6fv99SIu#ZWv62y57tU*u6Fnz9hKV&FeI!G5({d z0npEmj|dz#+-PgePd@A7m$Z8x)eKslUJQ-bXZ;04qZ+4FpeeoTWU=yJ#%F$lNgvL@ zTlk5ZPZqjn8&U=!)(`$=sa)==h!Cj9Irbad74m?$=Im@EX}?jm%!@8SFq7w9)9(>8yA`O@NVK+ zK?&$qZMK|@4+J}|J>VzOe2N#e8Dkur2igc^@^?P|R3bJ*{3b89SUoSjElIl7$U`kx zh%bGU@%7xAyFAYOo4kz>&F*VZbpRL?PpZH1LmaPP;$lP2#qGs&+u-O{4rYj&d2MFV z)~jpKHRb|WJ%4xarod9t;qO2OI6Pcgp`s4rYiivh-1+TO#TslDcsNx;y#!F{p&RpL z%>S?9#VI%F${xyx)#Db0o?g zzuc+##I#|gJIx+#lsk7`SXBJx_d{``>hQ>-$#iC8KD{$U!9>bmRkN!7(e^bToGR!f zjr!NIKjRlZ3m<`po$45>N!!37FQ^QY8ez?wCN<9B6u}K?V4JZim-el}=mgmHi+{Cb zUuKbLN)WrSWPlk7IsDKShq%|Mj!bQmohc7M?^d9nYuhR*1lH#4zZA5g^@YdpKe`Ah zAJh<;*v3q04U^WJNPjnHY3Qvx=y*9t2g#&8wpV?fiElZ&&%o^#*hINvUnlfl)6q3A z@bHH+8A{DvDI&Q}xZs2|#HNw2tvK`yf#UkL+Dfx!3|lm>M9~Bv^z1?^ZV? znR9TXZo;3UF$^(YG=ar0GQ0%@$L+3rKAJ5(rh!9n%?F>2&9^LLtVRSp9=l@{T3bEK zWt0f8=-dH{-mYf!2=)xB=IO_BhYKNI5jtC)cx2Qd(YO|PNYO$tz>eJuLBXPutSMwb zc+A+CY=?nP9u1zJ5QD^VvbJcCVje`-3{jam(P6R;QrGrCe4k<23%^uDUrrd)tgjrV z(rPP=-Sl=@4V!QfOtKSyE!ObJ|%2ikJBxeq|R`*SU$yG7ah zx9opRl01G^Q3334g_(M-{V{V(M_&0oV-CBA&RImf*?!g=xh{(n0-D{GGTVJ-a4+(- z>LNKu1w!E$A4U0OZld#jZX4(F!x`A!L54s**buy_4~1-V9$XfP-HFQAy|VcFQzJhO zrCtaB%gWRX&vYtn|sG|93RkJ7Nb4)dhyYks zYl~c;{i4t?qzy83iAiVfyJ4VcV5IpHqKhI;`<1zps$)G?kT9?OwEKH@d6n8sz@b55 zRN#~Nd|cTxv|pB>*L*WexYTj|>}diQA$TJGNw90oO%3KfJ9W8JUAU@|0!5>~*AtjT zcPr^i9rmf}g6mi%)_puQRz-d?OoIU8#^bGl76ez}-f%c)7G7vGs4V>0aR%~v=zs}~ z{umA%fXZP)J)^41-fQ?RO92oS-{T?#LN;ZmA-d;)e)h$0=ox|$j#v8BCqY5TC(AQ3 zJcJET3Hjw;*ouk)Yi*upwJ`mLio(#0>|q&9Us>H8auL^Ttgdq}41{zpvMxEStByjz zywS@u6NNbcx%M<_Nnw0~k1K(#8NWPI)k+y@HeTDSyy8a3e_FI@A*3N8Ss6a}3>lM| zxV)-IG6Mt^L()t;(lwhTx)o;KUn`rD>90LdyL$9Z6MJ1rUa81csiI|s1@s8i62JP* z8&3)xyzfmwd`8bDbx44wN}2+Rj8K(sz2tjZo~GXO#VoX)ZJhaVEiuvWcWt*tAU|O# z%`L)H%mMkJ?m7HPA#O$$k?jo^738L_GM$uBY&lqg%49hHg9VHlLZl91j^}g>NCWS6 zi!@n1?H^8ZF z>Vw&}gF=z4H2@rW*V|rcfRwTeP0FanCU^lFo^9xu2imcVqtySW43wofR?ljnzp^8Y zm|S1gEDo$wh`2FW_lyKI!Vf>)VTD5zZ~6rZ--5v9R6qWp2#Ocmd+nQLu5pIQ;=@;2 zOtVj}n9S_#;Ewh;8j@kVcY2FeII8aLZkhA{tekg5=Yn}-1yVfWDr(IeXl+hJ_Of)r zfFBqG^i?>_YGVDjI2L-eeY_V!(x0t_&`esqCl|R?jKr7mC`Hn+65dIkNc_DTz;>Ng zZIn7lo=yT7bqoOn8q8`Z%fZJTG z3Cyh_c0szyJ=s8|r7|l7$`&7^KI~3LNoC4KV15O!Fz{SGVs%GH^vPd%y=3jllUVet zZaVjRgiXA``X0stCg}q3I=W6a2W0rK>o;N)>-HSv6+4u0SIVCvl~rY?f=L^GR)JD+ z+E!}ySp>BvU>|*8qOk7wv{b6U+W4&KH|#Uzl8~RtasdKC=<8_edBAWA{^|dDuBSBZ z&ico#J2mbt7n!D>NiUjZ>*@aAL0fBVr-=e2&=ywcV@aso&38M}v$S0c4Ysrs|d- zKe-Xv9y{iPkC%Z_@eRWO8Rn@hhrFl+rbx*F(t{2?tzx7L&|?jT*%ceR%V!&qn#(>s zJj5BF;xl!1uan-T0t7&Zrxj52{(4H6BVJh^Y6`-XbMqZ#Q2+VsR&S+y4LT(!v(I#~ zPT&*W%*Mw(SJviTF=-HCVyr&@QU~+_lu|tn!G2{YTk83qIH21bV4H;5mi|SW1(>J; z?ct#Y5`320M;|{jFvJ2+;+HF1m|Dl zWuup;bx(DI`h@w#kR7u1+$=)1y~X!1FUK&{c^G~#)#+Bd7e ziZplvHyv{ofG;Gub82JZ(E~SG+q%!kv=Z`qYfhQ6*%R<~m^4gyV>Nx}8lzP{fe8UO|2RiG^z*aI4P0MbOfL@XS7)S=J4H4Q7pgAOk_ zi{Mcjr82=341PZK2evmmB#-1fKj0)m|N2#&+EsHq)&yZT1@k0IF@TnV%kx>dQf+Xs zu85G9jh4evSz1IxoAf$mn>nKk?8uw(vI=+jzs~d%(5_BQ{Sn@ zc*puxKx$#BvRx&>kRC_6*8z9q*OCa&qeO)4e^X7W75c^bO9JNUeI{SvakEed>$(Q= z9k^<4+jFn-@t(g&*~^~ZKtLH%JRvMp+bL<|fI;>yLw3i@1h( zPnlnemM(ukriO}}h!N6K($alYxB z4B2a1JBeNGvp_1~ePC@9B~}1a4|3YyxSwfMS@m!Zkszz3P5mP=KKvF`2Q+87@StUHlcN*e5qnrbO zRg){RTX&0gCho8d%e^gQk|m(PXW4(fI}0Sa1!ID$!*tM*NBvj1Z_2u^oNYjRe=u-l z%b_$ThEuaO*T9%p{>m|-SCfBU&c#aLi6=?6jMt#Ps2?GVFfN(`S)m?6FolSi zbR=*oCchodv~yYjSDtr_R-w9E%*D+)3JvB$zmr^U8yS#EH58*9?kkoOhTWe{=Eih9 zMKe)a9V+n_0^az*MTfGYG9zhJn^t3toFl;a=&h0>+sCfY<6OH-+O$NxC%5Aw3TqT> z+@x$z{w3HVyQenUY%@4b+JhMR(0t+QBNL>&X%KM(f%C+DG6ckdy*R{ME)gm?XRzU- z@;@F9FF$|`R5fw~2N^?p!ejWK#t0irmkVFx)N5_xPGdnrAX#Og76+b8hw*sEeoASBCZewr4ZscmWITbUYpZp;nbUPU<#eMN^iCxuNu?w9(h>QM5rX?+Dr_!n!jeiQGf zOro0utG-ZfpD`|ukCTL@P_%6SScWDoW$5nGvs=4nLumm|W8m#j)7RUVium>*1z`+; zB4KBMz=7$ITymSqL9h(d6Pof?aIJkh+BCK0zi-Ow65P_C#vRkX%(jjj=;dplfKzHUl3aCSuf0Jq zRjEVZ$%CVft^^yBB?iTkkd-${Thc_Codu7^B{u9e1TZ~CuK2WM6K4Aiy!HJIj&Xlk!x^3_Mlx1=a#H6-ize+fRB5h4#Cr(y&ic2LRMwXr z^0Gw205G1UQd)5WO*q49JDR8?@f~J+S|ptS{DxkZ) z2;~oDRm=i+z$s?NT{e%zVAqygMc z6!WlgSfMb^(HUNY6%wi;5K8{Pi68i>Lujp|!z;~Ko)--1Li}VN>i0PATv2_E6Gz+P zKDS)AGtKmRW!fSZ>yHq`t~xWe5E-4+jOr5&uNvW4kiJucBAINMpEmnxj_?R^%`Lw1o7QDSJfbOmzQpqBA zP463x-}|@H`Mq5$Vi1m-r4|vB`Y!v3=3cZleXjN?<6n+pdDjbT$yL@JW`JumU zK`b8?uoM#7jg5e>T=-%)*x=@>>WTR#*VXaLHQ%@(pNtJBd{nPs*)uA+kZ=_3=`Jw^ zv5$zI2EaCFdWSHr#?>6=6{@Z1_&$)3yRuPKSHWby1x(*>8pe0C%o}QgX~5frilV%d zFj!z|fi_PY*Dcd79`pd7=`hYMb0IOS z*yFn0@Q-OPK3Lc`GhfcdQ+XP)AZ%l*!!zY0yB`=O6Kx;%jzo>#5E$3LI~MXa7ae~7 zmJDbbOj-^^D-$`n4Om8J_b~&R`CH=pYek@?yDwWZV)JBD@wjp+PnUiH)JWj%eq31< z@-jrGo(0aoU)^npuHH=sF=-L)pd6->A2dNUDjq5{)6tw>4u<{vFy<3lR*Qy@M4L_h zTnN_kE;{lU4Q?!h0Dc#_lse3)WG#b^pzD5_w((UNt-NIw6o!0^&-q;>Df|Wilw$jEh5O!-)SrIr zEX^m8{C5S&fsl=Qr0B|5+$?u734&qr=M`{3k>_&hdW69xDH>Yy z4R}_px~Zd~qSL>rbe>p{pZX3B;-(}_@=f@f2UK%d-qO|>91xFa93zoq6+%n(;Jb?H zMbS)Sy4nz^MkO78O7NQ!l1GL?vrqIY>l(97|3f7M&z=)bgMQbU#1uvfc|V%O#(ukv#f+2m(3N}QN&fK zwCZX3B2b|Tby+eQENszSi1wDzPu%6kh~4MsYPjQP_c2NY?Waj}KY=`t><7mJSJZap z1K>yPg^qSB@yb2VMe(I=`D7g(-k}+C1Hx!0WqtRk6l&UE&jre3nB`6uoT544QCu{spFxRU+|!X<5-O{%M5b$2-t1*1zVii1L( zq~Ux~<`t1rs`Mvr0B4oQ5|5v*hlg08q7 z98ZX;Ox~;Cv>k&k(RNV52mk}uJ&WRGnJ&&Q&4c+GbLf)G0H1NY3~s0rh(nOEug_mj zG924Z=hOD)jW${ z4QfJZa1T99uj&9PX6q^JlneaDEqv77T&!^03u~o4+Ua|B0k3Z7(A?I9j~R^AwQd`U z$NW0!-xH7I*c3_0PN~yc=MbUn_$QhDkOxgVDcx*A`U(mMsA-Ynttac|=Oh1{5a4FA zn9CNVYJGLK0(e<`gYs9Gi3|W{>Nb_cLT(TepB|~;3y6-T#@(^tho}shuR%DHfH;U@ zCDuOY>IVkL+}Yr}6+Zx%dL-rw0k?XY**ThAsspqt87bG!DoUs0R6?8Nrz^=hXAL)v zifxHVRBYbS?f6z7HdKCKGvF6ibg8d9z6_0Y07UO(XWc=(mH$?KgQ)9r?^&``s2296 zg}AWgFqjeTD9hx`Hb0?F3hU8-D$2~waFg?-wrdGAo z!wOIGZZt8~GajLjp4h!New%4uOuxE_&^{mco^4sFR5#OWmv`bdF(2;c|5sVH&%SfgYjnvvI||8^-8%>m_0Kg&SgvTuLJI9)9iT zyNFKbQbYn4JTb!`#D}?opAem-(I^47 z%q5c%`*R)zhX+TB!n|uTo1^0gfj`r__N+dpxj&GjzU|RVb(<*aCP3^#fsxb+V??bf zhc*FUGf7$FQi*$yw0(C;n2d*?&8oLO68k@`xx#)i!{=Sg?GHGmTSj0uhJY$r$El98 z1`N(HhRk7CL)mQ9_4<}*bSEqG1)girVAXfde1h-V<*;bLP0hk5;1P#N{~8kdz<>Fr zg7e`aKGKvjI)nXkSnwSw?X4 zC#(@s{+Ybdql=fIrzix>)7Rirk)Pdk!vE35+Vp;PBW09bu3CCxN5Dy22-t}5N>F$m z8wrS_tYJ%r_vx(cIY%+wuHtafxM~TLV99;nEq?}xeNCeW?l$i=jGV+^317?-RFJP! zOW2^&VDvi%wbyg34@WX_8Lfv|JrL&&vJXQ#*IWxXTo657R4YOxvenqP*hdzywpCw- znO<(xX;-}j3&E7o6Xe@ZO1@JcvOr@{!rR3Q7mzfj)yG#f9JZ^zOfUxKGLf3+`ucqe zIbA?G{l30e1B8{S(DhwRWHYIRsM;QNF z8MM7`(;ujC`YxT1I#)^rAO)}>IHK@J@rV0?S>OE>d7SqdHK_HZm6jbgEt9sx zxy-5F#{Ja1e!Yz2`n|epcgmxzQ>?>&CcPOHWnfPB_@$ZX=#=yRQG84W|7TZJ1TPcA zpJ0(u$w*Xt7pM5i$E;E)352nI-)znh5j!wf^u#ljJCzBqBW}+& z)zwY%#43_2MzC}#uGrC5+hC8zdl$#Y?n2wgx(S#nmQoyJzb$Cm%3>AzE&+``a={2; z9{i-(Iy_0prJGF#(XzQ^>i5NcmWNY$|5r=!Bb4vanfOyC7bu;i2gxv$kCOQs61O<4 z61eR`P{+X+L&xJKtIXWN4q43QqIrtRzrOnX-i(d`$)g;L(gdh60w*p?*uQ$hO(r|E zH7@)#^TucMP>bi>#=q<4Pu?DT{~x82R-psm(@BBp=*$2rCa5gRbaXPQOziJchz6(H&h`xv=z(Jfv{(1Rb|Kj(IaT{ zn20Vji|gHr^j@g=l%>rA+9)x#LkZGO6|v;@`Q>6I_x`~j=K?DB!+s~`rgxqyii|b> zoe0CCNR)PCUj~~y^Zt@Y#kf!_KZ>x`lC6x2p9KjJ`E2Mp8VO`)5<}8;_|zS{Wp&Z; zEr95%g>)?Di8Z{&&tt%jTjpVve4x_R`56|3MNsXHNnRBgd`*8 zN-0UW$vGlc1KcxqzO(JyV-!;cc|~_KI>UX^3tJW0sAy^ zGp*2|l8(*v^7bNd&l@-S_|E#p<|~Y)+4qG|)%jkS*>sD5BXnx(dZK}I)P@tO#VA+W z*_jo*JXy~)vf!i4vh~*tU{b+gSpOmMhowh5EgwuMQ23O_HhhYnk{hYQLCnTQsw)m! z1L)nwsxQuWYYz2JWyCj9nZIsah*Xu6m0cq~O1|e|~P9}2dkW%%LBcZyj9Mp8-^u4H7$UD z&sPsvfljWNRx1^_pYQ*eue2a5CBQT7%@RCWEPfZ5jB=^|@L&z&Q3?VM^K{8-ldyw%v*JUSkvrNr;#;g-hL?>?n4 zKH{1XPrH`GWmRBmDw)vX;+^OjMF*1fI}_=Fvu6nj{+^(k-;+i38DD-_%*Vo9zm)QG za!{$AK5q=F$K$2jiOF%y3pxUg7V#N;Yu0~x@**T$V3PcC4rQE>;`!oW!gw5YcwLVj z2GH3b_BxbIp7BG(+!I@n%Yku51q(DhS4Y0~H8vJhj!Mrzn!}1P+qQ@Wm!GoTjgFId z?RC~hu5XWm+XlN<%dfAd#9b}k;V+6ziah%mwt1dmlLGp7vz)O1Xip$zV5hIs8-Avr z!dh1sDR}4|(S*S|60E_|+PM+Te#R;@HBfCkdI4jk=0>TtW{cwnQHdKJLtwxsW@8D$ z8z!38ultF_TCXbJk2PSsoYtJWEy%8HpDuGCLmY8;)aU(}VJ@F)v-yEg5{ZAJ0`Q)Z zX&HHL5%D+)ahmx7+(6THy%cHbKKWYn=zw2My4}vbNkNKRP z(lL1{lTosyJD|%3eBXDL7UR5$RSNEj?NB$NvxtE>}zuC$ra zz=(rzpxq8gN$dqmYAf2Am+l#tXp0dCECLE;A&^g6X5PG_E3x~QV6D5f2*OXtsc1bK zNx4o0M4_=WSKa12+r#642BzigbTxrJSq)2{NPa|>VS}VRd8F$yC?FbiXE}}84S#n` zNvp!5jjm+?W5!fVt`t`1-Z#>BJvSX&?6|ef;W+=`@RvpOj5myHo~bLMFsnlvm&O*^0!5#ws7z~oi|^K>;W=Xvw1)~_4+s{ z^_SGylQB@3u%;m~m6Rye@@p(vap<_l!$|}u({%0R4>jwzq*6?l%@Z^#k!+2wjM;m8TCK$EVM=>Q zv!qMIAGuWoB<8t<0o87q>J$%Wus>&8t^9~cgXs^H5G2eROvEE3M}HDD5?YO_DmWe` zR$i^@Q8qwv83x(aD*#VR1>nsU3I_qbl{*NmqR2!I0}>BF!3kt^aM`XwASl}?Kebj9 znZWfrw`41E?bLx+PtHRn7RADng{bBaQc^OYK69;wmAw-20v~{+YuUE_4AALTOf&5d z&1X0eS7Zw95ALLFvVjCfzT|cR9WSe$gDq&h#;`MuFjRClI!%28<&Fi(VwEzkVgChZ zL5QIU6uCLGD#M(*1>zV+x7y#It+uD0T%!s<+0w=OIivAIvI?t37_~ZnN~s`UX|CLG zA2O#t(6TLX4}tYgpxI^0QZ9)CF%#ES?g>k9iaUONEbu|Be_ZF2( z2x(t>-6!R-<_2MJ-%8#s(;65A&sQ|soL+A=TqZ7dk^mM!KpEW{f_g2MZGeLa ze>yINv{AfS0MoJ}h$L(Z%QlKB*DK%ndG;2qXgue#()V?5J$AZma&z4HtN%&ZJlya1 zL#zgnXh%b$Hk!8hr0&hFQY-grw$Xz=#I}MTmp1k;%jiv76THe9theeGQ5GPa1QcJJ zfOLCY$@t{qv)U8ALZmFz4mqtvztvYQq%;3rN&@eU(}sG58?!NQ9T5>)1R09}huJvE zGOLC$-Jj8M+KcF2AFi(%UAMUv+qB>EJdKGHN^?ZoXdLt}TLhfrC>a78dk0KdH0Fh; z`3xeAFDmEzdU}(KyQlu_6KgGHnuaf_a5%mEnYYB(O0);=0Ln7(alhp-q(IWilR1bX zvonI+&W*UVgYByE-TIm(0fdJ)KWuNb8 zF{+m-xQ|X_Pu)+X(QMn~n-uWwG22d0KHoF+6#AP4uvIjT0be)(&nQ#nF8TF}J%flr$w-6J8B`iXbH(`JsB0$7fmyUD# zwVWiaaJ-a0L1fJwGhUIQ0es*{Mbi%30FpTK=5n)))1ko@>coEFry*UZvGUn3*8b4 zZyZkCF&@t#l4@AUD^XEy#rT_*RE=KQ7~>g;PRa!FpW*(&h`9P9F~zh4K*g@c-cTDC z#fudBz(l)Hj>w@LM_GGXsd7_iw=4a-R@d@Ob z+-{Kn(W~@mfT<2wYtj({KwH$D{5ZOVm;lFmdB-NM&*CMui+6#saDGYuVtrR4`Kr^L zPtGqYaZ#IunBKhyp$XKuEetNG51DhC#48D&i?kDI#zuJM@HU%RXpn##nv2aJ1YQMZ0L-GGg`L*v(!l_}Vi%*&Xhw zq9@Q!6Kn?BE-Np%1)K~mmvglfHbtlhJ^avPbNxbELG>jl(iX~$xKw#wq-b{JhoXHC zs84Vl#1p~d&u!1Un=RD^9T@uXy!%r2n7n-RdYX-1BoTL(3R15RXmOnKKe>@p8*!)uWQ9*ic-dW<-%v7P+iln;er#ZJ z_9LmhWu^=mU}^*ogoU?~?hR#URUxq=@3?0RywGfr;x@>$LSNvR4Z;z-b+8^Hyw&aX zvmdF74U~PL0i`68tj;jK#Qq$at2)ZRsSiZ^hs~1E+*ox(*yzsxzjUvw447EPK|V2H zkc7#qScQ0>1lJB-q(t>Nj*i4i!>$BIt@{}0d`_LLY)!UrB>`bY8NvQ`lR1*)UDiCI z>*o5ZBJ7)o{#)C`QGKXC>^cx-pQi|!T--PqI6LR!Rk6q)=Zr%%0UDy7HlrV#vY;pt zj2|~I_seER(k>~yzG+R?M255U%Xi$B5ZuPV&0BRW^kuOdGus-Z>7lP3M}prP%Y>J@ z{csj{Y&__$+&TGYoJ^UzcNWMtBPJxh2IpfZ0ez(yqx@U2J2^V4JOMN&q^a*Hr?g8w zc`Tz@@Ltf(FhPplwVvua&ScqJn7jqlzFBv?d za?lJF8FUrut6r$lcHuH5H^8{aN9|MQRrbdcVCghx1bi7`rgW>RHO*_e z?F>5R`yJo!3P5A^2AZe{wo_^)qZ}J10+BVQ3VRKc4!_M9z-8!P|5Ppj@Z8j{Bu^@^ z3QyKg)AUQ6GY>6wHs^{ctvLWsniZ(3$x5T7KENf-ppa6COZN>-DOGEckyQ0=EF)WA zC=mLzAZ5ug9khne<_1T5le}w9`-;wARBc>*6SWnDVDdu_?pvQ2hI*(<^8!46 zSoI=4>2rvgl|VVBE>gkJm3yUQ*wg_L&GQNlnOtM&wu}{LRYv7yDXl;^wx;-gt`Ifp0c#TwP%;=}%q2@jbuHjG>e_-W%E>$XrBGsFzyAmAz&s5S;)Qy7UHF z)JC9`NmuXp!;C*^Zf7Jv9MNukmMaI<`F9{fBnfp=RQ;PLynX8+H~wNBL~o(lzw}R* zKeAZyNOkp$zdXC+o!pC6In3#b zEZ=5a1PBWN#|@4#38o#M9KiYaH-h%8!e1#}^gSY-sCdX-N*%fNh0ninLzLP{k@Yex zQSu{@SU169A9GOFZR-Ql52dFa+IRpEaTf$7oZeFz%F5K;&b z7uSI5ffTlt#(8k(!(P&uemFrjr;3<#U?vCsFu&EVYfO@G1#nD z2M#6XnGk88k+Kc#^lV<`x$AEa-BG<=9Uo2cf5)~@1-iyi2!Z_;%u%Um>2_WHlKfhZ z!m6AHVF`&|&L9EH0e#?&%uL!>4kQpIaJmeXD$Xb#pRK!`spX>ImU)k+hImwZe5H05 z9!nGOa?^GuvhFVe0}~t|73~n0>jqYaM;L=`3nn>P%A^&K-J70^l?kEmJ2g}K6h5hN z`3z*Y97@bRB(7Qw&ST_D&!30>@-b*+V)s!+cVzmlwTW|`|6dQfH5vCQ!F`u+v zwU9cc2Nu@n)|#=+=t^v&6FO%Is%7C)tVVbmKb*Im)e-hBjOCMb>S7ITLfTHQJK=Vz zzH`KhaWw^IxQ^l4lzI<#pVaNar?$)rXrI#L^zn7h(Otgb{jH8CKzwd!l4Jbr<0)5 zqf_3zw?q|Nlqn76szZ_#V4Fgs+MxEX^@YjW!QvG%JH zV&!=#cn}t?{0~1_1)=o5Q&#ALxDmm^H)~q|pxBD(*nlmD*tvWSB&Y_!xxh;H0LG$! ziDAC&yryB;5cL>sy%S^la5=5$NmV@3)mg$5kzM<;^VEDA{)1wD5gAxUd5oL|nA$Ed z;a$_Nw#m7{qC}f&Op9l-1b*)Pcg*?Y7@IlZ+~(d=ov`O%=PB1*PZtzd{HmrbZ>DCXOm8f^2uXe#`mW zZBh=;vsvp#+>jtAWZ5tV*zS{l3NQwjHHX(62t(v2#q`=M2T_CAk_u1v9yRSm*A)1MyxXlx*AWS5Wz61e>*hYNFR z>YZf$*X-+A^yTx9_oNIAP{~%`&|D87E9^rrwTfh-Ls0+eOK5x7&3(YECYcZoo`4f2O44s(l-(^cSX?koYU~ zkbF743Dh}7#8vnsz=L-6jcqXgm=)sz_TCx1dU>MGV>P)IN(qL)gorSeF&VKhjvw(3 zIwOyj*|~yWdVRL{9MqD-2&=}=efBR{N*`>@{~iws_25<5a&o>27J&F-WknF}GdV_y z)Ysj!fRB+hnq|FV7$sHoJ`at7js!GljYL5_?S67IF+u|(3O0||Mm!ofqlAM9*>mjB z1;EyO!6I7NtUcr9Ao{f{?XTKQ&>$jpj50rB1I^PiG5v%7cx}4q(>Rc-!yeoc_&bX9 z`Tr@cxfI#W6qEQTd+5Uz?sSBBt{_b!#8s_f2-pk_d5T6`MhaAXct$-a_nl#vcZoDV z$N{e1z{GZA6RG`4G1saa-sV25OIRu#t8n%z=uuD6&aYRgo09gNnZ_f0E9%%N^0(_B z()+!OY)a-q?yD`O6oG9{FT&N0+)tj^N7k4t=S>>;d9KAzO+1p#^gevkFYQSikckC& z_rhlrf((#EWAOm2Q8PCUK%Y3D9$Ka;Adl2AIPYBAJB~&M>d@fb&qcl2yLs~LGbN)J zue=b%(W21jFAu3Xl6~+ozU)@s*h^S2Iq4a=y8Qe1MLz4jl5Dh(oVId4l2w713oE7q z4+Ou84;r3Wo^25SV19@VT2wmCCbTkHl{g3)FasRXJKHh{?lAFUI7{$i3)$;iV84}5 zeMROnn|@Wel;9W_z#oq`Nd2Xy)B#UybMk%&D^0;?m9qlGPR{4;F>I+ILgb_p^4Yih zcEX^KqyceNp;7h-$a0Iw4~lk2HNnYo;=e69kpLifX^JP!6(q6L5M&hl-#r+WhY)Q> zMCxS+D19bnemdCk4A*|L+Q}FF^zfv~S4l!+X1ZpKbdJ*>g^CF(@z_fm=A^xO89(x6SDVvk|@S(aVBpdK)|7K zAA4_IF>Pg-iC#?rLgbjEO;mQ_eH{43#-Iuq1_KQ)H$i3^l!1X8?`yqPN(zfvb-={EvK4`IO zp#MsZ)q9AsqeC^w*FH&LNPa8R#a{nAWw7QK`zWL*G=(x7fA1S+rWSdY)us}}mEyY? zbT~m01P>dX6*Ho2%j>u(PcdcR1k+}k7#UUs0}9Rgyv2L_o+fy5`)~zf#(G5tFFYdu z8CMC7gg;-sEq|BQ@Oqi1cO1a=G>rtcJO}MjYWd^z%-vA?t?C^R+nzGlfR|08o_s&6j6;$bpJ9%qU^5W@7=sey}rwh$_fDhhf zaEU~B|7-Gp(8&dvRw_G>05sio%dncu-=fafje$7eH|EKnfub!%F{d(}Gz-;hq3IX9 z=z1M*8OsO^fbQFK&0d(Ef`*kk(I{gH8zGP`#$(L)e8gDjg}R0{0bMBJF5Y{Ns({UV zUyQdrZ|lIMcDqurey98rlZ4PXtFG!eJ`akD-N6w`GJpTXJ_0%xih)yiRxx#8N(zR4 zx#}qcE$%Z6C!uK7>{Tzo1Qe#8n8AQ5<#XdGpaWfe14|^07P)U-oq6F0UtU>ldPWcO zk(5C>lwou;aR7Y{FkK7)>#^qf0U(5hA{-GmaShCzwWtAY1&B#o)(q;P+7H#jn{PKS zUdPy;KXXtS4B{;$V6P9xma&>`TOS7MO3}tXogQRpnlCA{dh{&<#YCJk8S?1Rc1p6DS zQfgkyzL&*O%i}a3D#eRK=iupTvSoyr66tgfOat*$Bk z;$2(1q=!kyN{FGl9BHuqaxRavGfcD=_b-5AcA{;!=J5nfhB+2Ig2jj;!2B8Cqiv{I z>4x#>F*2kUV9&nx?h(Y>Dq~Bx-+!JwmAS}xe)%OoiOAbP0#SH(7jcj$8B#YpPxwM9 zR#GTEwG}*#tCT9w<@-0xDmN~QAzxF0wq)(*r3TiSLew_u1&0f?1V zfFBNN=IndY{em$zCI}X@A57g~zra1UR=5Q3sP>oFd#rP|j4R|=CkK(%%Ns~64ibpXNR~s-9n*4JYH0Ns* zWhAJ;^LuZxW!PcFHZ0jqY7klo|G9OtSqG+#;vbIp7}wlA?gXA_t440we@19OvVXfr z!e(^2-@1y%o}HeY4u*Y;>sl`js&*^AJu}HKr9kp_oc8^&3{Jv1N7Okx<{t^#(RB&qkx z*I=&(N**TBp6&~`^d#w(Jil7OH@`m=!wh1;&RGvWPpD3qD0%dCQ32vvfIx(8MT_A@6nkCNK3~1 z{>X;neqJM`%IpAx8E&>Uxn)_Nrz8IW>50{O`#m@&zMJPYQC9TsUswUKSnWrEA%rbg z#KA&5Kb`26i4aYyfjV8)bR>(Jya7MV+VuiM&UF7+vfIIW%+zJvXa#iwl(tP}L1Z|( zZ41VM&qoOs5{3>G%7-vfAvdkQGa~Pv*@P-&+~Q?@x0`EE3dbc$zY;90@{6w;J-IAR z!qupg$$FgS+aBUzWs>;+LKJNEmw=!9(L6BtBS@4ZOcRcTR1|)@!G5Tdd?^d6Ax@bc z!WJe15vq)hvxnV3;>-VDKE$Ai`SXEEpdTIfBPg%#kY~t)448Ybdjm0$!mWxble~Tn zrxn%pC4@&*hZhNhFgEO@AvvTPX%_89x}nD*M6g= z`PyL?^H-xedtUn=w5Q7W`f&^4#XmS8S>ghQ1TDW?j_ose9*&Yto2(pN6Z%WB&`_c) zcp3d$k-x_+7F9ah5G)!uZOuv{VmFCUH|J5spq>aS2X-nHmmWxDDEo0$_nw7AK|Ic{ zpbE7A zER*2FMUv!B*d{^z4mvE;axDeKi%%Waoinme|E`*YF;P+Cl_N^~PF?u6NB_;pOx5~G zFAf=qg}^~Kh~Ps^j{f;AWyg5=zZK3S6xIO~T15_6=hNZf6$BRL9*&wD%O+sHJPSbM zv7bv`or(G}Qjc1~OgRmRD(IywGh5=EW~FZkw1Z!^H$fQFxC#=~c#=AlZq0Ot$+KZv zgd77kkigmnLGoQheAgIonckI7qpvE$Va_${u#>uhWFK)7W6r@)_{_Uq#kwF0ovPKI zqcjGeMR;bqLS}9wV-^-8q84PZ!I9FkGZ`?TW=Jd{Oh3>jARU}V$q!m(Q(JUGs7E3e zs{(v;Mp;ppE41RL;b%%WgD_(T8Hz}T8C>MRRb^Y4lk*`U5&6$Y!~mg$Jj?kI_0+-s z%MWDkos%d6cYb|-wdnY64-h4;sWtNgXmJ<{E?z=9*2T*7niBV2=Q3w6A4nwuwQorIY$#2Rz=)K<_1% zM#(sIIT$!n%yheG>c4DROE!EU+m-A2TV@g6Z1dec?puN9d)uoC+(<;ZQbX#T52#QF zM9E9-*yv!pA9&iDyg_;j3qAB3P6T1lR)qjys85eL3t5306`6&x6$dZ=(8Zdg?ZzZx zBuj%oeuVof85ho6f8wb-j~sZ^J9AL@IF)PKe*c z0(})8oQ$Dobn;>01(p3v$3)pyu95kok~fmkHbOy?$Sm8F7i|t9g=L`zJLo!nm7*D| z-^!0on?tmxf4YXXKg{Z7%GU&Lq9CR}GO3Db;@0|o6z%cSorX!Ybiu9%7~QN8Q36l6 zP+h=^1q9)iNM7b8`ROTiro^I8d3cQ%b(RG~lXDszPD zJ3ol!l!BhtGt?o1KzS5)tzFyOqs@5?tJ@rJz}-j6^)mY0pg0O#F8mG=HzyHlt@j&k zNFr5gSmU}4Zz~SI*DWO5pXTPumavV!IP4J}`b)Ls`!h2RNiFAZDiuQs z63x0JE75E=82mv1>C5jITlXBl)#KJ5Bj}2r-o{KZIX%`dH3YbE?ci99C-CMSVB%EV zE-D}|E*t>zf1#X~^J70zHO9`^Xg~Afr5VT5X5K#~U=;0QninaoTza+$8Jd=@M(W?4 zD3}Brsc-w@uw46o53c&zCrsSV6>dh7txfQk?-$2BisgNMhH4ssv)kK%Y5dZ~- zmW+iblLfeAu#AJ$Gbd!(pWQVDl1{{+he0XruB~gKa@p#iGD5O9=jyT{?c3KA0j2CP z@{yoO0-d4EE1Yw%*+3N}WnnU`MO{E3knGoHqBK1%(<6!ZV!uDK3wQT+*gCQ8yXBUB z5ifCOG~$5o4c_zN3CLgRyS0chhN$r=^HIX0 zPqwG8`M^&j^9K5TZA-aXd{4IHN`2S1`>#1F8F^c5Jx9In#!0 zu4WN;C7})DVMDm%$x#wS%!@MDM&ac3*LAd9I(OmisTfV$wrF_I=xtC^5hvVPX9n&s zruHr7Dly3=e=2BtiJXjf-j;RWBt4|ec&H3p@&NU>mDk!mldO`Sw};kpNaokyXCacY zhKe>8h$vdTp<6>cwx+Ef)U@V?C<3Y@!#HGq4rRa5JRiEyAg?F_7lTD%AEy!7_ zfDR+wM2U+!UR84^2|$+}0~E%e!av0P_glB-$sS#n zjaqm^5-u`nTJi=UVxs$3SCLuEbH1sDO0UR z2qIIeXtdJV8V+pSoHAB45`$-CbLM8;d*Qq7$0}hy4c8bY7<}@^H4J)VP*yU-aS{QG z=C@wmMHH~oWc@vSHm#(U7cUiy_HQIx(sOa-$hyo;O>EvIr%p|8AatD?Di!oCHrTdd zAQs1of9H|A>w#Pu3(&oIbdko;-*o~A7QR@N3aKk6w7S{Xok5U#CX^ecEGj)iV1B?1 zKsw%vx_e~>)&T}Rh;`_cm`Ht(8K6U#H78zU9g9Pa6Sd{2za$fm6&3-^v}c+2yWi*S zW_0iYa{M(XI8nfDLFl^WoWsw9i@-p?dUt~{ip0_adbuR zc0be$>DbB%Mt(n_DlfN@6=u1%e!kPN$@4p`kt0?_HL>4-E>P(AA`RdtShNfxdW}kh zg%QrH-TU<_x>|6z8Js!Sll{n4z5ky4>%vEP=QEEXepLhj2Zt*2(QL0 z*E9Bpq_Rop;c>beK8MUOR!^N7{)5OXOj^@I>?Y&>HmA$#o90xdxqVT-dM zzp8Ik(ejKF;t!&tV1)))eJ{(MPlmggG3Y15S^AA&0Q7ZAP2M=XK0CLL=)<%MWB7bO zdFJ%NsGRni+S1Nt+7$6T5G&f`#^ItopJX&KAJ$Our1jjjuSa$@T%Av`M0F0+G1qWd z+*)D@FM!U)fNjfb%+0y~>-qy42k*v>Rymev-D!}r4HT$Z%;UwTz$Y%qR%l`gY;_CL$)f_fThBv8;`9HEOB&(xi{dn!qR#&*U=bvWSP zJ`-kt>e|s&_t)Y_=q#hjVDb#cVV>&w$i5>?V&7~BJNVAiAsq@!>l!+_)oBj9;c*+- z*1ij0)H4^=_rPF5H*?57?dSdD3X=l3yvMg|md9x;i2d!IP)gq^0LWDOI{at$a|YF^ z%MHZC$u4_tzF5Q^1lrZRLKv<;)YB6HqBAclw)DT?r-nlv-|)kf$kRyrn3Cbn>?lh9ufhM z)T_Lx=11J$qfD4L*ri^o%IMH&G(-mw9Uu0u?u0UhP4-f57{QFe7i)kC~Uw8** z)ocbf@xCz~xSHIS(akQW!mCtULdMyH7-)LO^#8}g4kDqw{v5}H1B#?R71)&(;>j1c zs1(5H*v{d3oID$tP{jjGq#^l4_L~Kcw8nGncZ%V_CdZe7&pKu({?|3hTXG($(_nIz zsX;zg4!6^yDkqH?kY z4o^Ii>5oqjtiSvr=}B6g;}%a^&nrGgy9E&a@Yd@@#&PNuaZg4f@aD_Y?)AVS$*y7V z>d#^ym%IAhjzW9Cdk`B<(i94%JDR-%K;Z~h^Im$rCwrbPRd}|$#<|dEN?{59234!n zgSnL~sXn8voj!{>nCChu!s@`#2w(I~q~4MX=yWyhu1z6TG$%%=4|-vY4gR$C_rR!zLqDCPcxFR8?3>0?LJ zSnj;U5>0_WgKn2@cdtmJ*g?KPTkn<@57O^&*=mc)W=_z}5_cIbe)gHQX^bT3dq21; z?cCwlqOyGl-8g?>3446^YjUn*OtP;jG((kDo2e5l26+^pK{qAu61s@T2ZPhaB7WjS z5?f%6e<+7O_>hp#A&@H)N~ATGgXcBajwxrtw*2pi}qewiLb3* zFg0OpgKw{YJ}=C>psd4a^WbTM|LY|ew`zzDDs!MMGIsROt6@PBS!Jb-!t4ecD_%MhExpu8G^{DCfA``Cp3G zY!k+mcojB{IC@39`H5jtZ)7ir55t`J6pQz9(}5l~!&C$tFIA7i>x%f6(W#T=lF!AM z@=zWy>W}OHx4YRTN-B+79fa7Ulu7nL{r+*#!p3~MlvDF|3&vl<;)&8tyN!||*_jIZ zl82-Lo)i{-To$h5G1GR;*xtkd5&-plUks=_5P$ zJXoD_t?<#9WLf#q_KRy}jGA0_gG+evF(P`LmN z@lHu9IV9@rStoD4D&??>+mt$|dGOBSto;RlY8ng;mj{nv#Fo&XFGHZ)N2kx$56?7o z5yn}nF#=aItWk~1Gd|x0I==0%-8bBG63f^DX@}u3mRCRsAdoBU;|RTeHX9gh=S;9u zs@D`t%0>EI25F2Sby@=q8Y4FtMnIo%L`1P1fossnz+QCZp1WGCMMTu&{Qyn;rL6N@ z#WFdZBbP4yzgY=D4{4#p#b+A5LXz!R3>lc*PhLBoH7N6Aqa#X`Kd4E>!JZMO?UqVg zxl+@BBQV1l(5bYhKq+pz?9;Pw5~s00Ag}2%HEFAqBUH36)=L*-l5Y#~x^>xN1HJZc zbP=*HRjG_n<}=3j_FP4X5T?BKm%sS z$|6{qWP!>!vGia6R?d1*0%^C!S2$9{ADEhC_{08Y4XIg}))6739y!Le$Gxui)m4tq zYRGrZ5|*Vy|JiWKWbfnw3aOn1p=}uk3n6`KZ1n3f(*E>pq-8scO;p>)QEn5 z0rR@Du&HZ@6|HKd)fvW#=lm?x*Opzd!uQUW$uLobTTdxL#>h7dnZFWc&+SL{u-!I4($`1}d_FXE>UQ(uFHp?j;_eTIu^r0AR6ek! z(B1+mZ&Ku;O?SYYT;I8W1Ay@p`hE!R3g<@H@OVpE%W+O8=m`7 zY&T;62XMF->aPw2mpDuC4GW#LUuto{2fFEIGRY^Usn0?|@hMmVCziwUc-U;_32xHrMXEChZu zW1q+|-btEzFJlV4(+>$gKEb4@Yj`xVq=t+!n5FZI%3>?0qc4<+Qa>#e?Qss#PyeL7 z=>GR)&lR0k^OtW78=9z}v&*|Gd0=-neZStm6>yd-_%ID%~9+Q_KXUc$LrDpq;6c=Yct!Qr|eZXmtZK zd3nqDtIp+`9rtAv9obw%hY53Y!3kn`^M{dxv-M;AkIb0}N%JI3b{C5)Vv$)`7Wu3r zs+keOVSaM>D}quC3qu7Ytlt%@IQ`NYpSWdPdiN1I6V&qsL}Oh&bYTJ&@BGhA;QMTZ zjB1>*i!l65?jRdzFai>UITk2c7d#z6x=~mBY_RR~^j0uJXHjUPu6re~GgGW*wfyp& z`Xla5s;IgV*)Br2=S8Jdk-rgTW!ZdR1a9g~*~hD6XlEqTX2*iKor%xoGJBys`)Xjx z^x!M^KZFI&N&0<15rTijuoG;-9nR?~p~XwY>P2E2{B{oUMHl_Gh5E(j+M`aLys-DF zr|2g)wN@GbvTYHK<{g4)BtLm|fp#mC=Js&dm}Bv&mW&ul#iY5?-geycHEe-$c*HSo zzP_6;3dA}B@$r^Rr`&-LUw!;#hQe^1d8i z%DQBYo$Q}-%a75hD8}LLHo4d1t02p?GYYw}yVA&B-Pngo^0Gti9a>(_qj)mAyYX!} zP~)s|93Y{Ge9}wSkWml$?(4IgsgVxPjdffwe2FD2^bkKRX#d5B3>{+}M*-cX^%+b{ zsCBwYdoY=Y96Z**$qSADN0? zly-b1)!M;g%q}%FO#*?T55WSb#=_s3L7MVMmY?w8Na%LC;liK~6i0dg*J%N-$Z0GLh%p(xP=*ZvziSpXz3k&7aYVjcd)`XP`uMLvs zfk763Jf1a|_x0w+_-W{eM&d^wo@pIn|3XQL(o&vAnbvwrX%PtDWk;}NQz&xq2wtiR y(C { + ctx.save() + ctx.beginPath() + ctx.fillStyle = "#80808077"; + ctx.strokeStyle = "#80808022"; + ctx.fillRect(225, -1025, 2400, 450); + ctx.fillRect(-2950, -1025, 3100, 450); + ctx.fillRect(-7050, -1025, 2400, 450); + ctx.fillRect(-10575, -3975, 4525, 1025); + ctx.fillRect(-4650, -1700, 1700, 1100); + ctx.fillRect(-11150, -3575, 575, 3050); + ctx.fillRect(-11900, -1000, 750, 475); + ctx.fill() + ctx.stroke() + ctx.restore() + ctx.save() + ctx.beginPath() + ctx.fillStyle = "#d8dadf"; + ctx.strokeStyle = "#d8dadf"; + ctx.moveTo(-2950, -600); + ctx.lineTo(-3730, -1725); + ctx.lineTo(-3730, -600); + + ctx.moveTo(-4650, -600); + ctx.lineTo(-3925, -1725); + ctx.lineTo(-3925, -575); + + ctx.moveTo(-10575, -3425); //NE section + ctx.lineTo(-10100, -2975); + ctx.lineTo(-10575, -2975); + + // ctx.moveTo(-7625, -3800); + // ctx.lineTo(-6750, -2975); + // ctx.lineTo(-7625, -2975); + + ctx.moveTo(-7975, -2975); + ctx.lineTo(-7625, -3800); + ctx.lineTo(-7350, -2950); + + ctx.moveTo(-6750, -2975); + ctx.lineTo(-7075, -3800); + ctx.lineTo(-7350, -2950); + + // ctx.moveTo(-7975, -2975); + // ctx.lineTo(-7075, -3800); + // ctx.lineTo(-7075, -2975); + + ctx.moveTo(-11900, -950); + ctx.lineTo(-11900, -550); + ctx.lineTo(-11500, -550); + + ctx.fillRect(-3925, -1675, 200, 1075); + ctx.fillRect(-7625, -3800, 550, 875); + ctx.clearRect(-10600, -4000, 525, 475); + ctx.clearRect(-10100, -4000, 500, 300); + ctx.clearRect(-9625, -4000, 500, 175); + ctx.fillRect(-11125, -3600, 550, 50); + ctx.fillRect(-10600, -3400, 50, 425); + ctx.fillRect(-11925, -925, 45, 375); + ctx.fillRect(-3950, -1675, 75, 1100); + ctx.fillRect(-3925, -625, 950, 50); + ctx.fillRect(-4650, -600, 1700, 375); + ctx.fillRect(-14550, -2400, 2650, 2050); + //ctx.clearRect(-11050, -3000, 475, 50); + + ctx.moveTo(-11150, -3575); + ctx.lineTo(-10575, -2150); + ctx.lineTo(-10575, -3575); + + ctx.stroke() + ctx.fill() + ctx.restore() + boost.query() + slimePit.query() + if (Matter.Query.collides(dong, [player]).length > 0 && !(m.isCloak && tech.isIntangible) && m.immuneCycle < m.cycle) { + m.immuneCycle = m.cycle + m.collisionImmuneCycles; //player is immune to damage for 30 cycles + const dmg = 0.05 * Math.min(simulation.dmgScale, simulation.difficulty); + m.damage(dmg); + simulation.drawList.push({ //add dmg to draw queue + x: dong.position.x, + y: dong.position.y, + radius: Math.sqrt(dmg) * 200, + color: simulation.mobDmgColor, + time: simulation.drawTime + }); + } + for (let i = 0; i < mob.length; i++) { + if (Matter.Query.collides(dong, [mob[i]]).length > 0) { + const dmg = 1; + mob[i].damage(dmg, true); + simulation.drawList.push({ //add dmg to draw queue + x: dong.position.x, + y: dong.position.y, + radius: Math.sqrt(dmg) * 50, + color: simulation.mobDmgColor, + time: simulation.drawTime + }); + break + } + } + level.exit.drawAndCheck(); + ctx.beginPath() + ctx.fillStyle = '#68686822'; + ctx.fillRect(-25, -2175, 100, 200); + ctx.fill() + ctx.setLineDash([125 * Math.random(), 125 * Math.random()]); + ctx.moveTo(-3825, -975) + ctx.lineTo(dong.position.x, dong.position.y) + ctx.stroke(); + ctx.setLineDash([]); + simulation.drawList.push({ //add dmg to draw queue + x: dong.position.x, + y: dong.position.y, + radius: 10, + color: color.block, + time: 20 + }); + ctx.beginPath() + ctx.fillStyle = `rgba(68,68,68, ${3 * Math.sin(simulation.cycle * 0.015)})` + ctx.fillRect(-3000, -2175, 175, 25); + ctx.fillRect(-2850, -2300, 25, 150); + ctx.fillRect(-3000, -2300, 175, 25); + ctx.fillRect(-3000, -2425, 25, 150); + ctx.fillRect(-3000, -2425, 175, 25); + ctx.fill() + ctx.fillStyle = `rgba(68,68,68, ${5 * Math.sin(simulation.cycle * 0.015)})` + ctx.fillRect(-2725, -2425, 25, 275); + ctx.fillRect(-2725, -2425, 175, 25); + ctx.fillRect(-2575, -2425, 25, 275); + ctx.fillRect(-2725, -2300, 175, 25); + ctx.fill() + ctx.fillStyle = `rgba(68,68,68, ${7 * Math.sin(simulation.cycle * 0.015)})` + ctx.fillRect(-2450, -2425, 25, 275); + ctx.fillRect(-2450, -2175, 175, 25); + ctx.fill() + ctx.stroke(); + ctx.fillStyle = `#00FFFF22`; + ctx.fillRect(-7650, -2975 - 100, 600, 2375 + 100) + ctx.fill() + ctx.fillStyle = `#00FFFF66` + ctx.fillRect(-7650 + Math.floor(Math.random() * 600), -2975 - 100, 5, 2375 + 100) + ctx.fillRect(-7650 + Math.floor(Math.random() * 600), -2975 - 100, 5, 2375 + 100) + ctx.fillStyle = `rgba(68, 68, 68)` + ctx.fillRect(-7675, -3075, 50, 125); + ctx.fillRect(-7075, -3075, 50, 125); + ctx.fillRect(-7725, -3025, 75, 75); + ctx.fillRect(-7050, -3025, 75, 75); + ctx.fill() + for (let i = 0, len = body.length; i < len; ++i) { //push blocks away vertically + if (body[i].position.x > -7625 && body[i].position.x < -7075 && body[i].position.y > -2975 - 100 && body[i].position.y < -625) { + body[i].force.y -= simulation.g * body[i].mass + 0.012; + } + } + for (let i = 0, len = bullet.length; i < len; ++i) { //push bullets away vertically + if (bullet[i].position.x > -7625 && bullet[i].position.x < -7075 && bullet[i].position.y > -2975 - 100 && bullet[i].position.y < -625) { + bullet[i].force.y -= simulation.g * bullet[i].mass; + } + } + for (let i = 0, len = powerUp.length; i < len; ++i) { //push powerups away vertically + if (powerUp[i].position.x > -7625 && powerUp[i].position.x < -7075 && powerUp[i].position.y > -2975 - 100 && powerUp[i].position.y < -625) { + powerUp[i].force.y -= simulation.g * powerUp[i].mass + 0.12; + } + } + for (let i = 0, len = mob.length; i < len; ++i) { //push mobs away vertically + if (mob[i].position.x > -7625 && mob[i].position.x < -7075 && mob[i].position.y > -2975 - 100 && mob[i].position.y < -625) { + mob[i].force.y -= simulation.g * mob[i].mass + 0.0012; + } + } + if (m.pos.x > -7625 && m.pos.x < -7075 && m.pos.y > -2975 - 100 && m.pos.y < -625) { + player.force.y -= m.mass * simulation.g + (input.down ? 0 : 0.012 * 2); + + } + elevator.move() + }; + level.setPosToSpawn(30, -2000); //normal spawn + level.exit.x = 2775; + level.exit.y = -650; + level.defaultZoom = 1800 + simulation.zoomTransition(level.defaultZoom + 800) + document.body.style.backgroundColor = "#d8dadf"; + spawn.mapRect(-225, -1950, 350, 75); + spawn.mapRect(225, -1950, 50, 75); + spawn.mapRect(-250, -2025, 50, 150); + spawn.mapRect(250, -2025, 50, 150); + spawn.mapRect(-250, -2250, 50, 125); + spawn.mapRect(-225, -2325, 500, 100); + spawn.mapRect(250, -2250, 50, 125); + spawn.mapRect(-100, -2400, 250, 100); + spawn.mapRect(-25, -2475, 100, 100); + spawn.mapRect(125, -2350, 50, 50); + spawn.mapRect(-125, -2350, 50, 50); + spawn.mapRect(-50, -2425, 50, 50); + spawn.mapRect(50, -2425, 50, 50); + spawn.mapRect(-250, -2350, 50, 50); + spawn.mapRect(250, -2350, 50, 50); + spawn.mapRect(-75, -1975, 200, 50); + spawn.mapRect(-50, -2000, 150, 50); + spawn.mapRect(100, -1950, 50, 75); + spawn.mapRect(-75, -2250, 200, 50); + spawn.mapRect(-50, -2225, 150, 50); + spawn.mapRect(-2950, -1900, 3100, 900); + spawn.mapRect(225, -1900, 2875, 900); + spawn.mapRect(-2950, -600, 6050, 450); + spawn.mapRect(-3050, -500, 200, 350); + spawn.mapRect(-3150, -400, 200, 250); + spawn.mapRect(-3250, -300, 200, 150); + spawn.mapRect(2950, -1050, 150, 500); + spawn.mapRect(-4675, -1900, 1825, 200); + spawn.mapRect(-5325, -1900, 675, 900); + spawn.mapRect(-5325, -250, 2100, 100); + spawn.mapRect(-5325, -600, 675, 450); // - + spawn.mapRect(-4700, -500, 150, 350); + spawn.mapRect(-4650, -400, 200, 250); + spawn.mapRect(-4550, -300, 200, 150); + spawn.mapRect(-3875, -1025, 100, 100); + spawn.mapRect(-3800, -1050, 50, 50); + spawn.mapRect(-3900, -1050, 50, 50); + spawn.mapRect(-3800, -950, 50, 50); + spawn.mapRect(-3900, -950, 50, 50); + spawn.mapRect(-6925, -1175, 1700, 175); + spawn.mapRect(-6925, -600, 1725, 175); + spawn.mapRect(-7700, -600, 800, 425);// - + spawn.mapRect(-7800, -2950, 175, 2775); + spawn.mapRect(-7075, -2950, 175, 1950); + spawn.mapRect(-9150, -2975, 1525, 175); + spawn.mapRect(-7075, -2975, 1150, 175); + spawn.mapRect(-6100, -3900, 175, 1100); + spawn.mapRect(-9150, -3975, 3225, 175); + spawn.mapRect(-9175, -3850, 75, 75); + spawn.mapRect(-9625, -3825, 500, 150); + spawn.mapRect(-9650, -3725, 75, 75); + spawn.mapRect(-10100, -3700, 500, 150); + spawn.mapRect(-10100, -2975, 975, 175); + spawn.mapRect(-10125, -3600, 75, 75); + spawn.mapRect(-10575, -3575, 500, 150); + spawn.mapRect(-10575, -2975, 500, 175); + spawn.mapRect(-11325, -2975, 250, 175); + spawn.mapRect(-11325, -3575, 175, 775); + // spawn.mapRect(-11325, -3575, 800, 150); + spawn.mapRect(-11225, -2975, 150, 2000); + spawn.mapRect(-10575, -2975, 150, 2500); + spawn.mapRect(-11650, -550, 1225, 150); + spawn.mapRect(-11650, -1100, 575, 150); + spawn.mapRect(-14675, -2525, 2925, 150); + spawn.mapRect(-11900, -2525, 150, 1575); + spawn.mapRect(-11850, -1100, 250, 150); + spawn.mapRect(-11875, -550, 275, 150); + spawn.mapRect(-11900, -550, 150, 350); + spawn.mapRect(-14675, -2525, 150, 2300); + spawn.mapRect(-14675, -375, 2925, 175); + spawn.mapRect(2725, -625, 250, 50); + spawn.mapRect(2625, -1025, 100, 225); + spawn.mapRect(2700, -1025, 300, 125); + spawn.mapRect(2625, -612.5, 125, 50); + spawn.mapRect(-3950, -1725, 250, 50); + spawn.mapRect(-7650, -3825, 600, 50); + spawn.mapRect(-13900, -2400, 200, 50); + spawn.mapVertex(-11957, -430, '-175 175 0 175 0 0'); + spawn.mapVertex(-14470, -430, '175 175 0 175 0 0'); + spawn.mapVertex(-11957, -2319, '-175 -175 0 -175 0 0'); + spawn.mapVertex(-14470, -2319, '0 0 0 -175 175 -175'); + //spawn.mapRect(-13900, -2150, 1375, 125); + const sword = function () { //the ultimate blade of las destruction + mobs.spawn(player.position.x, player.position.y, 5, 30, "transparent"); + let me = mob[mob.length - 1]; + Matter.Body.setDensity(me, 1); + me.vertices = Matter.Vertices.rotate(me.vertices, Math.PI, me.position); + me.collisionFilter.category = cat.bullet; + me.collisionFilter.mask = cat.mob | cat.mobBullet; + me.isDropPowerUp = false; + me.isShielded = true; + me.showHealthBar = false; + me.isUnblockable = true; + me.leaveBody = false; + me.isBadTarget = true; + me.stroke = "transparent"; + me.isSword = true; + let index = 0; + let radius = 50; + me.do = function () { + this.health = Infinity;//just in case + for (let i = 0; i < mob.length; i++) { + if (Matter.Query.collides(this, [mob[i]]).length > 0 && !mob[i].isSword) { + const dmg = 0.25;//do not nerf + mob[i].damage(dmg, true); + simulation.drawList.push({ //add dmg to draw queue + x: mob[i].position.x, + y: mob[i].position.y, + radius: Math.sqrt(dmg) * 50, + color: simulation.mobDmgColor, + time: simulation.drawTime + }); + break + } + } + Matter.Body.setPosition(this, { + x: player.position.x + Math.cos(m.angle) * 100, + y: player.position.y - (input.down ? 0 : 30) + Math.sin(m.angle) * 100 + }) + Matter.Body.setAngle(this, m.angle + Math.PI * 2); + const setNoseShape = () => { + const mag = radius + radius * 10; + this.vertices[2].x = this.position.x + Math.cos(this.angle) * mag; + this.vertices[2].y = this.position.y + Math.sin(this.angle) * mag; + this.vertices[4].x = this.position.x + Math.cos(this.angle) * mag; + this.vertices[4].y = this.position.y + Math.sin(this.angle) * mag; + this.vertices[0].x = this.position.x + Math.cos(this.angle) * mag; + this.vertices[0].y = this.position.y + Math.sin(this.angle) * mag; + }; + const spike = Vector.mult(Vector.normalise(Vector.sub(this.vertices[2], this.position)), radius * 100) + const spike2 = Vector.mult(Vector.normalise(Vector.sub(this.vertices[4], this.position)), radius * 500) + const spike3 = Vector.mult(Vector.normalise(Vector.sub(this.vertices[0], this.position)), radius * 500) + this.vertices[2].x = this.position.x + spike.x / 100 + this.vertices[2].y = this.position.y + spike.y / 100 + this.vertices[4].x = this.position.x + spike2.x / 75 + this.vertices[4].y = this.position.y + spike2.y / 75 + this.vertices[0].x = this.position.x + spike3.x / 75 + this.vertices[0].y = this.position.y + spike3.y / 75 + if (index == 0) { + setNoseShape(); + index++; + } + ctx.save() + ctx.beginPath(); + const vertices = this.vertices; + ctx.lineWidth = 100; + ctx.moveTo(vertices[0].x, vertices[0].y); + for (let j = 1, len = vertices.length; j < len; ++j) ctx.lineTo(vertices[j].x, vertices[j].y); + ctx.lineTo(vertices[0].x, vertices[0].y); + const gradient = ctx.createRadialGradient(this.position.x, this.position.y, 15, this.position.x, this.position.y, Math.abs(275 * Math.sin(simulation.cycle / 50)) + 15); + // Add three color stops + gradient.addColorStop(0, m.eyeFillColor); + gradient.addColorStop(0.9, "white"); + gradient.addColorStop(1, "darkgray"); + ctx.fillStyle = gradient; + ctx.strokeStyle = "transparent"; + ctx.shadowBlur = 10; + ctx.shadowColor = m.eyeFillColor; + ctx.fill(); + ctx.stroke(); + ctx.restore() + + const Dx = Math.cos(m.angle); + const Dy = Math.sin(m.angle); + let xElec = this.position.x + 10 * Dx; + let yElec = this.position.y + 10 * Dy; + ctx.beginPath(); + ctx.moveTo(xElec, yElec); + const step = 40 + for (let i = 0; i < 6; i++) { + xElec += step * (Dx + 1.5 * (Math.random() - 0.5)) + yElec += step * (Dy + 1.5 * (Math.random() - 0.5)) + ctx.lineTo(xElec, yElec); + } + ctx.strokeStyle = m.eyeFillColor; + ctx.lineWidth = 1.5; + ctx.setLineDash([70 + 300 * Math.random(), 55 * Math.random()]); + ctx.stroke(); // Draw it + ctx.setLineDash([]); + if (this.alive && m.energy > 0) { + const gradient = ctx.createRadialGradient(this.position.x, this.position.y, 0, this.position.x, this.position.y, Math.abs(20 * Math.cos(simulation.cycle / 50))); + // Add three color stops + gradient.addColorStop(0, m.eyeFillColor); + gradient.addColorStop(0.9, "white"); + gradient.addColorStop(1, "gray"); + ctx.save() + ctx.beginPath() + ctx.moveTo(this.position.x, this.position.y) + ctx.arc(this.position.x, this.position.y, 20, 0, 2 * Math.PI) + ctx.fillStyle = gradient; + ctx.strokeStyle = "transparent"; + ctx.shadowBlur = 10; + ctx.shadowColor = m.eyeFillColor; + ctx.fill() + ctx.stroke() + ctx.restore() + m.energy -= 0.002; + + ctx.save() + ctx.translate(this.vertices[2].x, this.vertices[2].y) + ctx.rotate(m.angle + Math.PI / 2) + ctx.beginPath() + ctx.font = "16px Arial"; + ctx.fillStyle = "black"; + ctx.strokeStyle = "black"; + // ctx.fillText("Θ", 0,0 - 110) + // ctx.fillText("ά", 0,15 - 110) + // ctx.fillText("ν", 0,30 - 110) + // ctx.fillText("α", 0,45 - 110) + // ctx.fillText("τ", 0,60 - 110) + // ctx.fillText("ο", 0,75 - 110) + // ctx.fillText("ς", 0,90 - 110) + ctx.fillText("Ω", 0, 55) + ctx.fill() + ctx.stroke() + ctx.restore() + + simulation.drawList.push({ + x: this.position.x + Math.floor(Math.random() * 300 - Math.random() * 300), + y: this.position.y + Math.floor(Math.random() * 300 - Math.random() * 300), + radius: 2, + color: m.eyeFillColor, + time: simulation.drawTime + }); + } else { + this.death() + powerUps.activated = false + } + } + } + //setTimeout(function() {sword();}, 100); + const wire = function () { + const breakingPoint = -1600; + const spawnx = -13800 + Math.floor(Math.random() * 100 - Math.random() * 100); + mobs.spawn(spawnx, -2375, 0, 2, "transparent"); + let me = mob[mob.length - 1]; + let boss = mob[0]; + me.collisionFilter.category = cat.body; + me.collisionFilter.mask = cat.map; + me.g = 0.0003; //required for gravity + me.restitution = 0; + me.stroke = "transparent" + me.freeOfWires = false; + me.frictionAir = 0.01; + me.isDropPowerUp = false; + me.showHealthBar = false; + me.isBadTarget = true; + me.isUnblockable = true; + const wireX = spawnx; + const wireY = -2375; + //const randomw = Math.floor(Math.random() * 100 - Math.random() * 100); + const width = Math.abs(10 + Math.floor(Math.random() * 10 - Math.random() * 10)); + const randomx = Math.floor(30 * Math.random() - 30 * Math.random()); + const randomy = Math.floor(10 * Math.random() - 10 * Math.random()) + me.do = function () { + if (this.freeOfWires) { + this.gravity(); + } else { + if (boss.position.y > breakingPoint) { + this.freeOfWires = true; + this.force.y -= -0.0006; + this.force.x += Math.random() * boss.velocity.x / 10000; + this.fill = "#111"; + } + //move mob to mob + Matter.Body.setPosition(this, { + x: boss.position.x + randomx, + y: boss.position.y + randomy + }) + } + //draw wire + ctx.beginPath(); + ctx.moveTo(wireX, wireY); + ctx.quadraticCurveTo(wireX, -100, this.position.x, this.position.y); + ctx.lineWidth = width; + ctx.lineCap = "butt"; + ctx.strokeStyle = "#111"; + ctx.stroke(); + ctx.lineCap = "round"; + }; + } + + const ball = function (x, y, radius = 11 * tech.bulletSize, sides = 70) {//superball //also, why is it called superballs? + mobs.spawn(x, y, sides, radius, "rgba(0,0,0)"); + let me = mob[mob.length - 1]; + me.stroke = "transparent"; + me.onHit = function () { + simulation.drawList.push({ //add dmg to draw queue + x: this.position.x, + y: this.position.y, + radius: 20, + color: simulation.mobDmgColor, + time: simulation.drawTime + }); + }; + Matter.Body.setDensity(me, 0.00001); //normal is 0.001 + me.timeLeft = 500; + me.friction = 0; + me.restitution = 1; + me.leaveBody = false; + me.isDropPowerUp = false; + //me.inertia = Infinity; + me.isBadTarget = true; + me.isMobBullet = true; + me.showHealthBar = false; + me.collisionFilter.category = cat.mobBullet; + me.collisionFilter.mask = cat.bullet | cat.player | cat.map | cat.body; + let index = 0; + me.do = function () { + this.timeLimit(); + this.alwaysSeePlayer() + this.force.y += this.mass * 0.0012; + } + } + const normalBullet = function (x, y, radius = 9, sides = 3) { + //bullets + mobs.spawn(x, y, sides, radius, "rgba(0,0,0)"); + let me = mob[mob.length - 1]; + me.stroke = "transparent"; + me.vertices = Matter.Vertices.rotate(me.vertices, Math.PI, me.position); + me.onHit = function () { + simulation.drawList.push({ //add dmg to draw queue + x: this.position.x, + y: this.position.y, + radius: 20, + color: simulation.mobDmgColor, + time: simulation.drawTime + }); + }; + Matter.Body.setDensity(me, 0.00004); //normal is 0.001 + me.timeLeft = 220; + me.frictionAir = -0.01; + me.restitution = -1; + me.leaveBody = false; + me.isDropPowerUp = false; + //me.inertia = Infinity; + me.isBadTarget = true; + me.isMobBullet = true; + me.showHealthBar = false; + me.collisionFilter.category = cat.mobBullet; + me.collisionFilter.mask = null; + let index = 0; + me.do = function () { + this.timeLimit(); + this.alwaysSeePlayer() + const setNoseShape = () => { + const mag = this.radius + this.radius * 10; + this.vertices[1].x = this.position.x + Math.cos(this.angle) * mag; + this.vertices[1].y = this.position.y + Math.sin(this.angle) * mag; + const angle = Math.atan2(player.position.y - this.position.y, player.position.x - this.position.x); + Matter.Body.setAngle(this, angle); + }; + const spike = Vector.mult(Vector.normalise(Vector.sub(this.vertices[1], this.position)), radius * 1000) + this.vertices[1].x = this.position.x + spike.x / 100 + this.vertices[1].y = this.position.y + spike.y / 100 + if (index == 0) { + setNoseShape(); + index++; + } + if (Matter.Query.collides(this, map).length > 0 || Matter.Query.collides(this, body).length > 0) { + const slow = 0.69 //im sorry it looks cool though + Matter.Body.setVelocity(this, { + x: this.velocity.x * slow, + y: this.velocity.y * slow + }); + simulation.drawList.push({ //add dmg to draw queue + x: this.position.x, + y: this.position.y, + radius: 10, + color: '#000000', + time: simulation.drawTime + }); + if (this.velocity.x == 0 && this.velocity.y == 0) { + this.death(); + } + this.frictionAir += 0.0001; + Matter.Body.setAngularVelocity(this, 0) + } + if (Matter.Query.collides(this, [player]).length > 0 && !(m.isCloak && tech.isIntangible) && m.immuneCycle < m.cycle) { + m.immuneCycle = m.cycle + m.collisionImmuneCycles; //player is immune to damage for 30 cycles // I wasnt gonna add this but since ya'll would have killed me if I didn't I added this + const dmg = 0.013 * simulation.dmgScale; + m.damage(dmg); + simulation.drawList.push({ //add dmg to draw queue + x: this.position.x, + y: this.position.y, + radius: Math.sqrt(dmg) * 200, + color: '#000000', + time: simulation.drawTime + }); + } + }; + } + const foamBullet = function (x, y, radius = 9, sides = 70) { //bullets + mobs.spawn(x, y, sides, radius, "rgb(0,0,0)"); + let me = mob[mob.length - 1]; + me.stroke = "transparent"; + Matter.Body.setDensity(me, 0.00005); //normal is 0.001 + me.timeLeft = 120; + // me.g = 0.0005; //required if using this.gravity + me.accelMag = 0.00006; + me.isVerticesChange = true + me.delay = 360 * simulation.CDScale; + me.spikeVertex = 0; + me.spikeLength = 0; + me.isSpikeGrowing = false; + me.spikeGrowth = 0; + me.isSpikeReset = false; + me.frictionAir = 0; + me.restitution = 0; + me.leaveBody = false; + me.isDropPowerUp = false; + me.isBadTarget = true; + me.isMobBullet = true; + me.showHealthBar = false; + me.isUnblockable = true; + me.collisionFilter.category = cat.mobBullet; + me.collisionFilter.mask = cat.body //| cat.bullet;// | cat.player; + me.do = function () { + if (this.distanceToPlayer2() < 40000) { + this.force = Vector.mult(Vector.normalise(Vector.sub(player.position, this.position)), this.mass * 0.004) + const slow = 0.99999999999999999; + Matter.Body.setVelocity(this, { + x: this.velocity.x * slow, + y: this.velocity.y * slow + }); + } + // this.gravity(); + this.timeLimit(); + // for (let i = 0, len = this.vertices.length; i < len; i++) { + // const dist = Vector.sub(this.seePlayer.position, this.vertices[i]); + // const distMag = Vector.magnitude(dist); + // const spike = Vector.mult(Vector.normalise(Vector.sub(this.vertices[i], this.position)), radius * distMag) + // this.vertices[i].x = this.position.x + spike.x / 100 + // this.vertices[i].y = this.position.y + spike.y / 100 + // } + if (this.radius < 50) { + const scale = 1.05; + Matter.Body.scale(this, scale, scale); + this.radius *= scale; + } + if (Matter.Query.collides(this, map).length > 0 || Matter.Query.collides(this, body).length > 0 && this.speed < 10) { + const slow = 0.97 + Matter.Body.setVelocity(this, { + x: this.velocity.x * slow, + y: this.velocity.y * slow + }); + const SCALE = 0.9 + Matter.Body.scale(this, SCALE, SCALE); + this.radius *= SCALE; + if (this.radius < 1) { + this.death() + } + } else { + this.attach(); + } + + }; + me.attach = function () { + if (Matter.Query.collides(this, [player]).length > 0) { + Matter.Body.setPosition(this, player.position) + if (player.speed > 2.5) Matter.Body.setVelocity(player, Vector.mult(player.velocity, 0.94)) + Matter.Body.setAngularVelocity(player, player.angularVelocity * 0.9); + m.damage(0.00003); //balanced? not sure + } + } + }; + + const orbital = function (who, radius, phase, speed, radius2) {//basically orbitBot + mobs.spawn(who.position.x, who.position.y, 8, 12, "rgba(0,0,0, 1)"); + let me = mob[mob.length - 1]; + me.stroke = "transparent"; + Matter.Body.setDensity(me, 0.01); //normal is 0.001 + me.leaveBody = false; + me.isDropPowerUp = false; + me.isBadTarget = true; + me.showHealthBar = false; + me.isOrbital = true; + me.isShielded = true + me.collisionFilter.category = cat.mobBullet; + me.collisionFilter.mask = cat.bullet; //cat.player | cat.map | cat.body + me.do = function () { + //if host is gone + if (!who || !who.alive) { + this.death(); + return + } + //set orbit + const time = simulation.cycle * speed + phase + const orbit = { + x: Math.cos(time), + y: Math.sin(time) + } + Matter.Body.setPosition(this, Vector.add(Vector.add(who.position, who.velocity), Vector.mult(orbit, radius + radius2))) + //damage player + if (Matter.Query.collides(this, [player]).length > 0 && !(m.isCloak && tech.isIntangible) && m.immuneCycle < m.cycle) { + m.immuneCycle = m.cycle + m.collisionImmuneCycles; //player is immune to damage for 30 cycles + const dmg = 0.013 * simulation.dmgScale + m.damage(dmg); + simulation.drawList.push({ //add dmg to draw queue + x: this.position.x, + y: this.position.y, + radius: Math.sqrt(dmg) * 200, + color: simulation.mobDmgColor, + time: simulation.drawTime + }); + //this.death(); + } + }; + } + const sniper = function (x, y, radius = 30) { + mobs.spawn(x, y, 8, radius, '#00000000'); + let me = mob[mob.length - 1]; + me.accelMag = 0.0003 + me.stroke = 'transparent'; + //me.isBoss = true; + me.searchTarget = map[Math.floor(Math.random() * (map.length - 1))].position; + me.frictionStatic = 0; + me.friction = 0; + me.seeAtDistance2 = 20000000 //14000 vision range + me.vertices = Matter.Vertices.rotate(me.vertices, Math.PI, me.position); + Matter.Body.rotate(me, Math.random() * Math.PI * 2); + me.showHealthBar = false + Matter.Body.setDensity(me, 0.01) + me.fireDir = { x: 0, y: 0 } + me.seePlayerFreq = 0 + me.repulsionRange = 400000 + radius * radius; + me.collisionFilter.mask = cat.bullet | cat.player | cat.body | cat.map | cat.mob | cat.mobBullet + me.do = function () { + this.seePlayerCheck(); + this.attraction(); + this.repulsion(); + this.search() + if (this.seePlayer.recall) { + const h = this.radius * 0.3; + const w = this.radius * 2; + const x = this.position.x - w / 2; + const y = this.position.y - w * 0.7; + ctx.fillStyle = "rgba(100, 100, 100, 0.3)"; + ctx.fillRect(x, y, w, h); + ctx.fillStyle = "rgba(0,255,255,0.7)"; + ctx.fillRect(x, y, w * this.health, h); + } + if (this.health < 1) { + this.health += 0.0005; //regen + } + ctx.save() + ctx.translate(this.position.x, this.position.y) + ctx.rotate(Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x)) + if (this.seePlayer.recall > 0 && this.distanceToPlayer2() > this.repulsionRange) { + var grd2 = ctx.createLinearGradient(0, 0, -150, 0); + // grd2.addColorStop(0, 'rgba(255, 255, 155, 0.8)'); + // grd2.addColorStop(1, 'rgba(255, 200, 0, 0.1)'); + grd2.addColorStop(0, 'rgba(150, 200, 255, 0.7)'); + grd2.addColorStop(1, 'rgba(150, 200, 255, 0)'); + ctx.fillStyle = grd2; + ctx.beginPath(); + ctx.moveTo(-18, -25); + //10 * (Math.random() - 0.5), 10 * (Math.random() - 0.5) + ctx.lineTo(-18, 25); + ctx.lineTo(-50 - 100 * Math.random(), 0); + ctx.fill(); + } else if (this.distanceToPlayer2() < this.repulsionRange) { + var grd2 = ctx.createLinearGradient(0, 0, 80, 0); + grd2.addColorStop(0, 'rgba(150, 200, 255, 0.7)'); + grd2.addColorStop(1, 'rgba(150, 200, 255, 0)'); + ctx.fillStyle = grd2; + ctx.beginPath(); + ctx.moveTo(20, -16); + //10 * (Math.random() - 0.5), 10 * (Math.random() - 0.5) + ctx.lineTo(20, 16); + ctx.lineTo(35 + 43 * Math.random(), 0); + ctx.fill(); + } + ctx.restore() + ctx.save() + ctx.translate(this.position.x, this.position.y) + ctx.rotate(Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x)) + ctx.beginPath(); + ctx.arc(0, 0, 30, 0, 2 * Math.PI); + ctx.fillStyle = m.bodyGradient + ctx.fill(); + ctx.arc(15, 0, 4, 0, 2 * Math.PI); + ctx.strokeStyle = "#333"; + ctx.lineWidth = 2; + ctx.stroke(); + ctx.beginPath(); //eye + ctx.arc(15, 0, 3.5, 0, 2 * Math.PI); + ctx.fillStyle = `rgba(255, 0, 0, ${this.health * this.health})`; + ctx.fill() + ctx.restore() + //set direction to turn to fire + if (this.seePlayer.recall && !(simulation.cycle % 30)) { + this.seePlayer.recall -= 10; + this.fireDir = Vector.normalise(Vector.sub(this.seePlayer.position, this.position)); + spawn.sniperBullet(this.position.x, this.position.y, 7 + Math.ceil(this.radius / 15), 5); + const v = 10 + 8 * simulation.accelScale; + Matter.Body.setVelocity(mob[mob.length - 1], { + x: this.velocity.x + this.fireDir.x * v, + y: this.velocity.y + this.fireDir.y * v + }); + } + }; + } + const laserEM = function (x, y, radius = 30) { + mobs.spawn(x, y, 8, radius, '#00000000'); + let me = mob[mob.length - 1]; + me.accelMag = 0.0003 + me.stroke = 'transparent'; + //me.isBoss = true; + me.frictionStatic = 0; + me.friction = 0; + me.seeAtDistance2 = 20000000 //14000 vision range + me.vertices = Matter.Vertices.rotate(me.vertices, Math.PI, me.position); + Matter.Body.rotate(me, Math.random() * Math.PI * 2); + me.showHealthBar = false + Matter.Body.setDensity(me, 0.01) + me.seePlayerFreq = 0 + me.searchTarget = map[Math.floor(Math.random() * (map.length - 1))].position; + me.swordDamage = 0.025 * simulation.dmgScale + me.collisionFilter.mask = cat.bullet | cat.player | cat.body | cat.map | cat.mob | cat.mobBullet + me.repulsionRange = 50000; + me.do = function () { + this.repulsion(); + this.search() + if (this.seePlayer.recall) { + const h = this.radius * 0.3; + const w = this.radius * 2; + const x = this.position.x - w / 2; + const y = this.position.y - w * 0.7; + ctx.fillStyle = "rgba(100, 100, 100, 0.3)"; + ctx.fillRect(x, y, w, h); + ctx.fillStyle = "rgba(0,255,255,0.7)"; + ctx.fillRect(x, y, w * this.health, h); + } + if (this.health < 1) { + this.health += 0.0005; //regen + } + if (this.seePlayer.recall) { + this.laserSword(this.position, Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x), 500 * Math.random()); + } + ctx.save() + ctx.translate(this.position.x, this.position.y) + ctx.rotate(Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x)) + if (this.seePlayer.recall > 0 && this.distanceToPlayer2() > this.repulsionRange) { + var grd2 = ctx.createLinearGradient(0, 0, -150, 0); + // grd2.addColorStop(0, 'rgba(255, 255, 155, 0.8)'); + // grd2.addColorStop(1, 'rgba(255, 200, 0, 0.1)'); + grd2.addColorStop(0, 'rgba(150, 200, 255, 0.7)'); + grd2.addColorStop(1, 'rgba(150, 200, 255, 0)'); + ctx.fillStyle = grd2; + ctx.beginPath(); + ctx.moveTo(-18, -25); + //10 * (Math.random() - 0.5), 10 * (Math.random() - 0.5) + ctx.lineTo(-18, 25); + ctx.lineTo(-50 - 100 * Math.random(), 0); + ctx.fill(); + } else if (this.distanceToPlayer2() < this.repulsionRange) { + var grd2 = ctx.createLinearGradient(0, 0, 80, 0); + grd2.addColorStop(0, 'rgba(150, 200, 255, 0.7)'); + grd2.addColorStop(1, 'rgba(150, 200, 255, 0)'); + ctx.fillStyle = grd2; + ctx.beginPath(); + ctx.moveTo(20, -16); + //10 * (Math.random() - 0.5), 10 * (Math.random() - 0.5) + ctx.lineTo(20, 16); + ctx.lineTo(35 + 43 * Math.random(), 0); + ctx.fill(); + } + ctx.restore() + ctx.save() + ctx.translate(this.position.x, this.position.y) + ctx.rotate(Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x)) + ctx.beginPath(); + ctx.arc(0, 0, 30, 0, 2 * Math.PI); + ctx.fillStyle = m.bodyGradient + ctx.fill(); + ctx.arc(15, 0, 4, 0, 2 * Math.PI); + ctx.strokeStyle = "#333"; + ctx.lineWidth = 2; + ctx.stroke(); + ctx.beginPath(); //eye + ctx.arc(15, 0, 3.5, 0, 2 * Math.PI); + ctx.fillStyle = `rgba(255, 0, 0, ${this.health * this.health})`; + ctx.fill() + ctx.restore() + this.seePlayerCheck(); + this.attraction(); + } + me.laserSword = function (where, angle, length) { + const sub = Vector.sub(this.seePlayer.position, this.position) + const unit = Vector.normalise(sub) + const path = [{ + x: this.position.x + 20 * Math.cos(this.angle), + y: this.position.y + 20 * Math.sin(this.angle) + }, + { + x: this.position.x + (120 + 400) * Math.sqrt(Math.random()) * Math.cos(this.angle), + y: this.position.y + (120 + 400) * Math.sqrt(Math.random()) * Math.sin(this.angle) + } + ]; + this.seePlayer.recall -= 3; + const vertexCollision = function (v1, v1End, domain) { + for (let i = 0; i < domain.length; ++i) { + let vertices = domain[i].vertices; + const len = vertices.length - 1; + for (let j = 0; j < len; j++) { + results = simulation.checkLineIntersection(v1, v1End, vertices[j], vertices[j + 1]); + if (results.onLine1 && results.onLine2) { + const dx = v1.x - results.x; + const dy = v1.y - results.y; + const dist2 = dx * dx + dy * dy; + if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) best = { x: results.x, y: results.y, dist2: dist2, who: domain[i], v1: vertices[j], v2: vertices[j + 1] }; + } + } + results = simulation.checkLineIntersection(v1, v1End, vertices[0], vertices[len]); + if (results.onLine1 && results.onLine2) { + const dx = v1.x - results.x; + const dy = v1.y - results.y; + const dist2 = dx * dx + dy * dy; + if (dist2 < best.dist2) best = { x: results.x, y: results.y, dist2: dist2, who: domain[i], v1: vertices[0], v2: vertices[len] }; + } + } + }; + best = { x: null, y: null, dist2: Infinity, who: null, v1: null, v2: null }; + const look = { x: where.x + length * Math.cos(angle), y: where.y + length * Math.sin(angle) }; + // vertexCollision(where, look, body); // vertexCollision(where, look, mob); + vertexCollision(where, look, map); + if (!m.isCloak) vertexCollision(where, look, [player]); + if (best.who && (best.who === player) && m.immuneCycle < m.cycle) { + m.immuneCycle = m.cycle + m.collisionImmuneCycles; //player is immune to damage for an extra second + m.damage(this.swordDamage); + simulation.drawList.push({ //add dmg to draw queue + x: best.x, + y: best.y, + radius: this.swordDamage * 1500, + color: "rgba(80,0,255,0.5)", + time: 20 + }); + } + if (best.dist2 === Infinity) best = look; + ctx.beginPath(); //draw beam + ctx.moveTo(where.x, where.y); + ctx.lineTo(best.x, best.y); + ctx.strokeStyle = "#50f"; + ctx.lineWidth = 1.5; + ctx.setLineDash([70 + 300 * Math.random(), 55 * Math.random()]); + ctx.stroke(); // Draw it + ctx.setLineDash([]); + ctx.lineWidth = 20; + ctx.strokeStyle = "rgba(80,0,255,0.07)"; + ctx.stroke(); // Draw it + + const Dx = Math.cos(Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x)); + const Dy = Math.sin(Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x)); + let xElec = this.position.x + 40 * Dx; + let yElec = this.position.y + 40 * Dy; + ctx.beginPath(); + ctx.moveTo(xElec, yElec); + const step = 40 + for (let i = 0; i < 6; i++) { + xElec += step * (Dx + 1.5 * (Math.random() - 0.5)) + yElec += step * (Dy + 1.5 * (Math.random() - 0.5)) + ctx.lineTo(xElec, yElec); + } + ctx.strokeStyle = "#50f"; + ctx.lineWidth = 1.5; + ctx.setLineDash([70 + 300 * Math.random(), 55 * Math.random()]); + ctx.stroke(); // Draw it + ctx.setLineDash([]); + } + }; + if (powerUps.pass == undefined) { + let pass = { pass: true, activated: false }; + Object.assign(powerUps, pass) + } + const loadOut = { + loadOut: { + name: "loadOut", + color: "#000000", //"hsl(248,100%,65%)", + size() { return 40 }, + effect() { + if (m.alive) { + // tech.damage *= 2; + let text = ""; + if (!tech.isSuperDeterminism) { text += `
${tech.isCancelTech ? "?" : "✕"}
`; }; + text += `

Blessing Of Sal

`; + text += `
  Speed Boost
Increase speed by 5%
`; + text += `
  Defense Boost
Reduce damage by 5%
`; + text += `
  Damage Boost
Increase damage by 10%
`; + if (powerUps.pass == true) { + text += `
  Blade of Sal
Press Shift to summon the Mythical Las Slayer
Drains Energy
`; + } + document.getElementById("choose-grid").innerHTML = text; + powerUps.showDraft();//no known bugs ig idk, im keep this as it is + } + }, + choose(index) { + if (index == 1) { + tech.squirrelFx += 0.25; + tech.squirrelJump += 0.1; + m.setMovement(); + powerUps.endDraft("buff"); + } else if (index == 2) { + simulation.dmgScale *= 0.95; + powerUps.endDraft("buff"); + } else if (index == 3) { + m.dmgScale *= 1.1; + powerUps.endDraft("buff"); + } else if (index == 4) { //sword! + powerUps.pass = false; + addEventListener("keydown", function (event) { + if (event.key == "Shift" && powerUps.activated == false) { + sword() + powerUps.activated = true; + } else if (event.key == "Shift" && powerUps.activated == true) { + for (let i = 0; i < mob.length; i++) { + if (mob[i].isSword) { + mob[i].death() + } + powerUps.activated = false; + } + } + }) + powerUps.endDraft("buff"); + } + } + } + } + Object.assign(powerUps, loadOut) + const restoreBoss = function (x, y, radius = 30) { + mobs.spawn(x, y, 8, radius, 'transparent'); + let me = mob[mob.length - 1]; + me.stroke = 'transparent'; + let aim = '#FFFFFF'; + me.accelMag = 0.0006 + me.isBoss = true; + me.restoreBoss = true; + me.frictionStatic = 0; + me.friction = 0; + me.seeAtDistance2 = 20000000 //14000 vision range + me.vertices = Matter.Vertices.rotate(me.vertices, Math.PI, me.position); //make the pointy side of triangle the front + Matter.Body.rotate(me, Math.random() * Math.PI * 2); + me.showHealthBar = false + Matter.Body.setDensity(me, m.maxHealth / (simulation.difficulty < 5 ? 0.5 : simulation.difficulty / simulation.difficultyMode)) + me.seePlayerFreq = 0 + me.swordDamage = 0.025 * simulation.dmgScale + me.collisionFilter.mask = cat.bullet | cat.player | cat.body | cat.map | cat.mob | cat.mobBullet + me.repulsionRange = 500000; + me.isDropPowerUp = false; + //Matter.Body.setVelocity(me, { x: 10 * (Math.random() - 0.5), y: 10 * (Math.random() - 0.5) }); + let index = 0; + me.energy = 1; + me.maxEnergy = 1; + me.immuneCycle = 0; + me.cycle = 0; + me.onDeath = function () { + powerUps.spawn(this.position.x, this.position.y, "loadOut"); + } + for (let i = 0; i < b.totalBots(); i++) { //normal orbitals look too boring, so... + orbital(me, 190 + 130 * tech.isOrbitBotUpgrade, (index / b.totalBots()) * 2 * Math.PI, 0.05, Math.floor(Math.sin(simulation.cycle / 10) * 100)); //who, radius, phase, speed + index++; + } + me.do = function () { + this.cycle++; + if (this.seePlayer.recall) { //fields + if (m.fieldMode == 0 && this.distanceToPlayer2() < 200000) { + if (Vector.magnitude(Vector.sub(m.pos, this.position)) - this.radius < m.fieldRange && Matter.Query.ray(map, m.pos, this.position).length === 0) { + this.pushM(); + } + this.drawField(); + // this.repel(); + } + if (m.fieldMode == 2) { + if (this.distanceToPlayer2() < 200000) { + if (Vector.magnitude(Vector.sub(m.pos, this.position)) - this.radius < m.fieldRange && Matter.Query.ray(map, m.pos, this.position).length === 0) { + this.pushM(); + } + this.drawField() + } + if (tech.isPerfectBrake) { //cap player and bullet speed around restoreBoss //mobs basically can't hit you when you have this, so to make it fair... + const wave = Math.cos(m.cycle * 0.022); + const range = 200 + 140 * wave + 150 * m.energy + const distance = Vector.magnitude(Vector.sub(this.position, m.pos)) + const cap = this.immuneCycle > this.cycle ? 8 : 4 + if (distance < range) { + if (player.speed > cap && Vector.dot(player.velocity, Vector.sub(this.position, m.pos)) > 0) { // if velocity is directed towards player + Matter.Body.setVelocity(player, Vector.mult(Vector.normalise(player.velocity), cap)); //set velocity to cap, but keep the direction + } + } + for (let i = 0; i < bullet.length; i++) { + const distance2 = Vector.magnitude(Vector.sub(this.position, bullet[i].position)) + if (distance2 < range) { + if (bullet[i].speed > cap && Vector.dot(bullet[i].velocity, Vector.sub(this.position, bullet[i].position)) > 0) { // if velocity is directed towards player + Matter.Body.setVelocity(bullet[i], Vector.mult(Vector.normalise(bullet[i].velocity), cap)); //set velocity to cap, but keep the direction + } + } + } + ctx.beginPath(); + ctx.arc(this.position.x, this.position.y, range, 0, 2 * Math.PI); + ctx.fillStyle = "rgba(0,0,0,0.08)"; + ctx.fill(); + } + } + if (m.fieldMode == 5 && this.distanceToPlayer2() < 200000) { + this.laserSword(this.position, Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x), 500 * Math.random()); + } + if (m.fieldMode == 6) { + this.timeAttack(); + } + if (m.fieldMode == 9) { + if (this.distanceToPlayer2() < 300000) { + this.teleportAway() //blink but reversed + } + } + } + if (m.immuneCycle > m.cycle) { + me.damageReduction = 0; + } else { + me.damageReduction = 1; + } + this.repulsion(); + this.seePlayerCheck(); + this.attraction(); + this.lostPlayer(); + if (this.seePlayer.recall) { + const h = this.radius * 0.3; + const w = this.radius * 2; + const x = this.position.x - w / 2; + const y = this.position.y - w * 0.7; + ctx.fillStyle = "rgba(10, 10, 10, 0.3)"; + ctx.fillRect(x, y, w, h); + ctx.fillStyle = "rgba(0,0,0,0.7)"; + ctx.fillRect(x, y, w * this.energy, h); + } + if (this.health < 1) { + this.health += 0.0001; //regen + } else if (this.health < 1) { + this.health += 0.00005; //reduced regen + } + if (this.energy < 0) {//energy thingy + this.energy = 0; + } else if (this.energy > this.maxEnergy) { + this.energy = this.maxEnergy; + } else if (this.energy < this.maxEnergy) { + this.energy += 0.001; + } + ctx.save() + ctx.translate(this.position.x, this.position.y) + ctx.rotate(Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x)) + if (this.seePlayer.recall > 0 && this.distanceToPlayer2() > this.repulsionRange) { + ctx.globalAlpha = (this.immuneCycle < this.cycle) ? 1 : 0.5 + var grd2 = ctx.createLinearGradient(0, 0, -150, 0); + // grd2.addColorStop(0, 'rgba(255, 255, 155, 0.8)'); + // grd2.addColorStop(1, 'rgba(255, 200, 0, 0.1)'); + grd2.addColorStop(0, 'rgba(150, 200, 255, 0.7)'); + grd2.addColorStop(1, 'rgba(150, 200, 255, 0)'); + ctx.fillStyle = grd2; + ctx.beginPath(); + ctx.moveTo(-18, -25); + //10 * (Math.random() - 0.5), 10 * (Math.random() - 0.5) + ctx.lineTo(-18, 25); + ctx.lineTo(-50 - 100 * Math.random(), 0); + ctx.fill(); + } else if (this.distanceToPlayer2() < this.repulsionRange) { + ctx.globalAlpha = (this.immuneCycle < this.cycle) ? 1 : 0.5 + var grd2 = ctx.createLinearGradient(0, 0, 80, 0); + grd2.addColorStop(0, 'rgba(150, 200, 255, 0.7)'); + grd2.addColorStop(1, 'rgba(150, 200, 255, 0)'); + ctx.fillStyle = grd2; + ctx.beginPath(); + ctx.moveTo(20, -16); + //10 * (Math.random() - 0.5), 10 * (Math.random() - 0.5) + ctx.lineTo(20, 16); + ctx.lineTo(35 + 43 * Math.random(), 0); + ctx.fill(); + } + ctx.restore() + ctx.save() + ctx.globalAlpha = (this.immuneCycle < this.cycle) ? 1 : 0.5 + ctx.translate(this.position.x, this.position.y) + ctx.rotate(Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x)) + if (this.health > 0.5) { + ctx.beginPath(); + ctx.arc(0, 0, 30, 0, 2 * Math.PI); + ctx.fillStyle = m.bodyGradient + ctx.fill(); + ctx.arc(15, 0, 4, 0, 2 * Math.PI); + ctx.strokeStyle = "#333"; + ctx.lineWidth = 2; + ctx.stroke(); + ctx.beginPath(); //eye + ctx.arc(15, 0, 3.5, 0, 2 * Math.PI); + ctx.fillStyle = `rgba(255, 0, 0, ${this.health * this.health})`; + ctx.fill() + ctx.restore() + } else { + ctx.beginPath(); + ctx.arc(0, 0, 30, 0, 2 * Math.PI); + ctx.fillStyle = m.bodyGradient + ctx.fill(); + ctx.strokeStyle = "#333"; + ctx.lineWidth = 2; + if (!(Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x) > -Math.PI / 2 && Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x) < Math.PI / 2)) ctx.scale(1, -1); //here is the flip + ctx.stroke(); + ctx.beginPath(); + ctx.arc(2, -6, 7, 0, 2 * Math.PI); + ctx.stroke(); + ctx.beginPath(); + ctx.arc(25, -6, 7, 0.25 * Math.PI, 1.6 * Math.PI); + ctx.stroke(); + ctx.beginPath(); + ctx.arc(2, -10, 9, 1.25 * Math.PI, 1.75 * Math.PI); + ctx.stroke(); + ctx.beginPath(); + ctx.arc(25, -10, 9, 1.25 * Math.PI, 1.4 * Math.PI); + ctx.stroke(); + ctx.beginPath(); + ctx.arc(18, 13, 10, 0, 2 * Math.PI); + ctx.fillStyle = m.bodyGradient; + ctx.fill(); + ctx.stroke(); + ctx.beginPath(); + ctx.arc(18, 13, 6, 0, 2 * Math.PI); + ctx.fillStyle = "#555"; + ctx.fill(); + ctx.stroke(); + ctx.beginPath(); + ctx.arc(3, -6, 3, 0, 2 * Math.PI); + ctx.fill(); + ctx.stroke(); + ctx.beginPath(); + ctx.arc(26, -6, 3, 0, 2 * Math.PI); + ctx.fill(); + ctx.stroke(); + ctx.restore(); + } + if (m.fieldMode == 1) { //render over I think + if (this.energy > 0.1) { + this.harmonic3Phase(); + } + } + if (m.fieldMode == 3) { + const angle = Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x); + me.damageReduction = 0.5; + me.accelMag = 0.0012; + if (!(simulation.cycle % Math.floor(100 + 90 * Math.random() * simulation.CDScale))) { + this.diveAttack() + } + ctx.beginPath(); + ctx.arc(this.position.x, this.position.y, 1000, 0, 2 * Math.PI); + ctx.fillStyle = "#f5f5ff"; + ctx.strokeStyle = "#f5f5ff55"; + ctx.setLineDash([125 * Math.random(), 125 * Math.random()]); + ctx.globalCompositeOperation = "difference"; + ctx.fill(); + ctx.globalCompositeOperation = "source-over"; + ctx.stroke() + ctx.setLineDash([]); + } else { + me.accelMag = 0.0006; + } + if (this.immuneCycle > this.cycle) { + this.damageReduction = 0; + } else { + if (m.fieldMode == 3) { + this.damageReduction = 0.5; + } else { + this.damageReduction = 1; + } + } + if (this.seePlayer.recall) { //fields + this.gun() + } + } + me.laserSword = function (where, angle, length) { + const sub = Vector.sub(this.seePlayer.position, this.position) + const unit = Vector.normalise(sub) + const path = [{ + x: this.position.x + 20 * Math.cos(this.angle), + y: this.position.y + 20 * Math.sin(this.angle) + }, + { + x: this.position.x + (120 + 400) * Math.sqrt(Math.random()) * Math.cos(this.angle), + y: this.position.y + (120 + 400) * Math.sqrt(Math.random()) * Math.sin(this.angle) + } + ]; + this.seePlayer.recall -= 3; + const vertexCollision = function (v1, v1End, domain) { + for (let i = 0; i < domain.length; ++i) { + let vertices = domain[i].vertices; + const len = vertices.length - 1; + for (let j = 0; j < len; j++) { + results = simulation.checkLineIntersection(v1, v1End, vertices[j], vertices[j + 1]); + if (results.onLine1 && results.onLine2) { + const dx = v1.x - results.x; + const dy = v1.y - results.y; + const dist2 = dx * dx + dy * dy; + if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) best = { x: results.x, y: results.y, dist2: dist2, who: domain[i], v1: vertices[j], v2: vertices[j + 1] }; + } + } + results = simulation.checkLineIntersection(v1, v1End, vertices[0], vertices[len]); + if (results.onLine1 && results.onLine2) { + const dx = v1.x - results.x; + const dy = v1.y - results.y; + const dist2 = dx * dx + dy * dy; + if (dist2 < best.dist2) best = { x: results.x, y: results.y, dist2: dist2, who: domain[i], v1: vertices[0], v2: vertices[len] }; + } + } + }; + best = { x: null, y: null, dist2: Infinity, who: null, v1: null, v2: null }; + const look = { x: where.x + length * Math.cos(angle), y: where.y + length * Math.sin(angle) }; + // vertexCollision(where, look, body); // vertexCollision(where, look, mob); + vertexCollision(where, look, map); + if (!m.isCloak) vertexCollision(where, look, [player]); + if (best.who && (best.who === player) && m.immuneCycle < m.cycle) { + m.immuneCycle = m.cycle + m.collisionImmuneCycles; //player is immune to damage for an extra second + m.damage(this.swordDamage); + simulation.drawList.push({ //add dmg to draw queue + x: best.x, + y: best.y, + radius: this.swordDamage * 1500, + color: "rgba(0,0,0,0.5)", + time: 20 + }); + } + if (best.dist2 === Infinity) best = look; + ctx.beginPath(); //draw beam + ctx.moveTo(where.x, where.y); + ctx.lineTo(best.x, best.y); + ctx.strokeStyle = "#000"; + ctx.lineWidth = 1.5; + ctx.setLineDash([70 + 300 * Math.random(), 55 * Math.random()]); + ctx.stroke(); // Draw it + ctx.setLineDash([]); + ctx.lineWidth = 20; + ctx.strokeStyle = "rgba(0,0,0,0.07)"; + ctx.stroke(); // Draw it + + const Dx = Math.cos(Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x)); + const Dy = Math.sin(Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x)); + let xElec = this.position.x + 40 * Dx; + let yElec = this.position.y + 40 * Dy; + ctx.beginPath(); + ctx.moveTo(xElec, yElec); + const step = 40 + for (let i = 0; i < 6; i++) { + xElec += step * (Dx + 1.5 * (Math.random() - 0.5)) + yElec += step * (Dy + 1.5 * (Math.random() - 0.5)) + ctx.lineTo(xElec, yElec); + } + ctx.strokeStyle = "#000"; + ctx.lineWidth = 1.5; + ctx.setLineDash([70 + 300 * Math.random(), 55 * Math.random()]); + ctx.stroke(); // Draw it + ctx.setLineDash([]); + } + me.drawField = function () { + if (m.fieldMode != 2) { + const angle = Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x) + const range = m.fieldRange; + ctx.beginPath(); + ctx.arc(this.position.x, this.position.y, range, angle - Math.PI * m.fieldArc, angle + Math.PI * m.fieldArc, false); + ctx.lineWidth = 2; + ctx.stroke(); + let eye = 13; + if (m.fieldMode == 2) { + eye = 30 + } + let aMag = 0.75 * Math.PI * m.fieldArc + let a = angle + aMag + let cp1x = this.position.x + 0.6 * range * Math.cos(a) + let cp1y = this.position.y + 0.6 * range * Math.sin(a) + ctx.quadraticCurveTo(cp1x, cp1y, this.position.x + eye * Math.cos(angle), this.position.y + eye * Math.sin(angle)) + a = angle - aMag + cp1x = this.position.x + 0.6 * range * Math.cos(a) + cp1y = this.position.y + 0.6 * range * Math.sin(a) + ctx.quadraticCurveTo(cp1x, cp1y, this.position.x + 1 * range * Math.cos(angle - Math.PI * m.fieldArc), this.position.y + 1 * range * Math.sin(angle - Math.PI * m.fieldArc)) + ctx.fill(); + // ctx.lineTo(this.position.x + eye * Math.cos(angle), this.position.y + eye * Math.sin(angle)); + + //draw random lines in field for cool effect + let offAngle = angle + 1.7 * Math.PI * m.fieldArc * (Math.random() - 0.5); + ctx.beginPath(); + eye = 15; + ctx.moveTo(this.position.x + eye * Math.cos(angle), this.position.y + eye * Math.sin(angle)); + ctx.lineTo(this.position.x + range * Math.cos(offAngle), this.position.y + range * Math.sin(offAngle)); + ctx.strokeStyle = "rgba(0,0,0,0.6)"; + ctx.lineWidth = 1; + ctx.stroke(); + } else { + ctx.beginPath(); + const wave = Math.cos(m.cycle * 0.022); + const angle = Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x) + ctx.arc(this.position.x, this.position.y, m.fieldRange, angle - Math.PI * m.fieldArc, angle + Math.PI * m.fieldArc, false); + ctx.lineWidth = 2.5 - 1.5 * wave; + ctx.stroke(); + const curve = 0.57 + 0.04 * wave + const aMag = (1 - curve * 1.2) * Math.PI * m.fieldArc + let a = angle + aMag + let cp1x = this.position.x + curve * m.fieldRange * Math.cos(a) + let cp1y = this.position.y + curve * m.fieldRange * Math.sin(a) + ctx.quadraticCurveTo(cp1x, cp1y, this.position.x + 30 * Math.cos(angle), this.position.y + 30 * Math.sin(angle)) + a = angle - aMag + cp1x = this.position.x + curve * m.fieldRange * Math.cos(a) + cp1y = this.position.y + curve * m.fieldRange * Math.sin(a) + ctx.quadraticCurveTo(cp1x, cp1y, this.position.x + 1 * m.fieldRange * Math.cos(angle - Math.PI * m.fieldArc), this.position.y + 1 * m.fieldRange * Math.sin(angle - Math.PI * m.fieldArc)) + ctx.fill(); + } + } + me.pushM = function () { + const unit = Vector.normalise(Vector.sub(this.position, player.position)) + if (tech.blockDmg) { + Matter.Body.setVelocity(player, { x: 0.5 * player.velocity.x, y: 0.5 * player.velocity.y }); + //draw electricity + const step = 40 + ctx.beginPath(); + for (let i = 0, len = 0.8 * tech.blockDmg; i < len; i++) { + let x = this.position.x - 20 * unit.x; + let y = this.position.y - 20 * unit.y; + ctx.moveTo(x, y); + for (let i = 0; i < 8; i++) { + x += step * (-unit.x + 1.5 * (Math.random() - 0.5)) + y += step * (-unit.y + 1.5 * (Math.random() - 0.5)) + ctx.lineTo(x, y); + } + } + if (m.immuneCycle < m.cycle) { + m.immuneCycle = m.cycle + m.collisionImmuneCycles + m.damage(0.025 * simulation.dmgScale) + } + ctx.lineWidth = 3; + ctx.strokeStyle = "#000"; + ctx.stroke(); + } + const massRoot = Math.sqrt(Math.min(12, Math.max(0.15, player.mass))); // masses above 12 can start to overcome the push back //idk + Matter.Body.setVelocity(player, { + x: this.velocity.x - (15 * unit.x) / massRoot, + y: this.velocity.y - (15 * unit.y) / massRoot + }); + } + me.diveAttack = function () { + const forceMag = this.accelMag * this.mass; + const angle = Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x); + this.force.x += 150 * forceMag * Math.cos(angle); + this.force.y += 150 * forceMag * Math.sin(angle); + ctx.beginPath() + ctx.moveTo(this.position.x + Math.sin(angle), this.position.y + Math.cos(angle)) + ctx.lineTo(this.seePlayer.position.x, this.seePlayer.position.y) + aim = '#000000'; + ctx.stroke() + } + me.teleportAway = function () {//hehe + ctx.beginPath(); + ctx.moveTo(this.position.x, this.position.y); + if (this.seePlayer.recall && !(simulation.cycle % 17)) { + const dist = Vector.sub(this.position, this.seePlayer.position); + const distMag = Vector.magnitude(dist); + const unitVector = Vector.normalise(dist); + const rando = (Math.random() - 0.5) * 50; + if (distMag < 20000) { + Matter.Body.translate(this, Vector.mult(unitVector, distMag + rando)); + } else { + Matter.Body.translate(this, Vector.mult(unitVector, 20000 + rando)); + } + } + ctx.lineTo(this.position.x, this.position.y); + ctx.lineWidth = radius * 2; + ctx.strokeStyle = "rgba(0,0,0,0.08)"; + ctx.stroke(); + if (!this.seePlayer.yes) { + ctx.beginPath(); + ctx.moveTo(this.position.x, this.position.y); + if (this.seePlayer.recall && !(simulation.cycle % 17)) { + const dist = Vector.sub(this.seePlayer.position, this.position); + const distMag = Vector.magnitude(dist); + const unitVector = Vector.normalise(dist); + const rando = (Math.random() - 0.5) * 50; + if (distMag < 200000) { + Matter.Body.translate(this, Vector.mult(unitVector, distMag + rando)); + } else { + Matter.Body.translate(this, Vector.mult(unitVector, 200000 + rando)); + } + } + ctx.lineTo(this.position.x, this.position.y); + ctx.lineWidth = radius * 2; + ctx.strokeStyle = "rgba(0,0,0,0.08)"; + ctx.stroke(); + } + } + me.timeAttack = function () { + if (!simulation.isTimeSkipping || input.field) { + requestAnimationFrame(() => { + simulation.timePlayerSkip(5) + m.walk_cycle += m.flipLegs * m.Vx * 5 + }); //just doing what lilgreenland did + } + } + me.harmonic3Phase = function () { //normal standard 3 different 2-d circles + if (tech.harmonics === 2) { + const fieldRange1 = (0.75 + 0.3 * Math.cos(m.cycle / 23)) * m.fieldRange * m.harmonicRadius + const fieldRange2 = (0.68 + 0.37 * Math.cos(m.cycle / 37)) * m.fieldRange * m.harmonicRadius + const fieldRange3 = (0.7 + 0.35 * Math.cos(m.cycle / 47)) * m.fieldRange * m.harmonicRadius + const netfieldRange = Math.max(fieldRange1, fieldRange2, fieldRange3) + ctx.fillStyle = "rgba(0,0,0," + Math.min(0.6, (0.04 + m.energy * (0.1 + 0.11 * Math.random()))) + ")"; + ctx.beginPath(); + ctx.arc(this.position.x, this.position.y, fieldRange1, 0, 2 * Math.PI); + ctx.fill(); + ctx.beginPath(); + ctx.arc(this.position.x, this.position.y, fieldRange2, 0, 2 * Math.PI); + ctx.fill(); + ctx.beginPath(); + ctx.arc(this.position.x, this.position.y, fieldRange3, 0, 2 * Math.PI); + ctx.fill(); + //360 block + if (Vector.magnitude(Vector.sub(player.position, this.position)) - this.radius < netfieldRange) { + me.pushM(); + } + for (let i = 0; i < bullet.length; i++) { + if (Vector.magnitude(Vector.sub(bullet[i].position, this.position)) - this.radius < netfieldRange) { + const dx = bullet[i].position.x - this.position.x; + const dy = bullet[i].position.y - this.position.y; + const dist = Math.sqrt(dx * dx + dy * dy); + if (dist < m.fieldRange) { + const angle = Math.atan2(dy, dx); + const mag = (1500 * bullet[i].mass * simulation.g) / dist; + bullet[i].force.x += mag * Math.cos(angle); + bullet[i].force.y += mag * Math.sin(angle); + } + this.energy -= 0.0012; + } + } + } else { + const rotation = simulation.cycle * 0.0031 + const phase = simulation.cycle * 0.023 + const radius = m.fieldRange * m.harmonicRadius + ctx.lineWidth = 1; + ctx.strokeStyle = "rgba(0,0,0,0.5)" + ctx.fillStyle = `rgba(0,0,0,${Math.min(0.6, m.energy * (0.11 + 0.1 * Math.random()) * (3 / tech.harmonics))})`; + // ctx.fillStyle = "rgba(0,0,0," + Math.min(0.7, m.energy * (0.22 - 0.01 * tech.harmonics) * (0.5 + 0.5 * Math.random())) + ")"; + for (let i = 0; i < tech.harmonics; i++) { + ctx.beginPath(); + ctx.ellipse(this.position.x, this.position.y, radius * Math.abs(Math.sin(phase + i / tech.harmonics * Math.PI)), radius, rotation + i / tech.harmonics * Math.PI, 0, 2 * Math.PI); + ctx.fill(); + ctx.stroke(); + } + //360 block + if (Vector.magnitude(Vector.sub(player.position, this.position)) - this.radius < radius) { + me.pushM(); + } + for (let i = 0; i < bullet.length; i++) { + if (Vector.magnitude(Vector.sub(bullet[i].position, this.position)) - this.radius < radius) { + const dx = bullet[i].position.x - this.position.x; + const dy = bullet[i].position.y - this.position.y; + const dist = Math.sqrt(dx * dx + dy * dy); + if (dist < m.fieldRange) { + const angle = Math.atan2(dy, dx); + const mag = (1500 * bullet[i].mass * simulation.g) / dist; + bullet[i].force.x += mag * Math.cos(angle); + bullet[i].force.y += mag * Math.sin(angle); + } + this.energy -= 0.0012; + } + } + } + } + me.railGun = function () { + const angle = Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x); + const X = this.position.x + const Y = this.position.y + const unitVector = { x: Math.cos(angle), y: Math.sin(angle) } + const unitVectorPerp = Vector.perp(unitVector) + + function magField(mag, arc) { + ctx.moveTo(X, Y); + ctx.bezierCurveTo( + X + unitVector.x * mag, Y + unitVector.y * mag, + X + unitVector.x * mag + unitVectorPerp.x * arc, Y + unitVector.y * mag + unitVectorPerp.y * arc, + X + unitVectorPerp.x * arc, Y + unitVectorPerp.y * arc) + ctx.bezierCurveTo( + X - unitVector.x * mag + unitVectorPerp.x * arc, Y - unitVector.y * mag + unitVectorPerp.y * arc, + X - unitVector.x * mag, Y - unitVector.y * mag, + X, Y) + } + ctx.fillStyle = `rgba(50,0,100,0.05)`; + const magSize = 8 * c * tech.railChargeRate ** 3 + const arcSize = 6 * c * tech.railChargeRate ** 3 + for (let i = 3; i < 7; i++) { + const MAG = magSize * i * i * (0.93 + 0.07 * Math.random()) + const ARC = arcSize * i * i * (0.93 + 0.07 * Math.random()) + ctx.beginPath(); + magField(MAG, ARC) + magField(MAG, -ARC) + ctx.fill(); + } + } + me.waves = []; + me.doLongitudinal = function () { + if (!m.isBodiesAsleep) { + ctx.strokeStyle = "rgba(0,0,0,0.6)" //"000"; + ctx.lineWidth = 2 * tech.wavePacketDamage + ctx.beginPath(); + // const end = 1100 * tech.isBulletsLastLonger / Math.sqrt(tech.waveReflections * 0.5) //should equal about 1767 + const end = 1100 * tech.isBulletsLastLonger * Math.pow(0.93, tech.waveReflections) //should equal about 1767 + const damage = 0.0005 * simulation.dmgScale//normal damage for m basically shreds m, so had to nerf this + for (let i = this.waves.length - 1; i > -1; i--) { + const v1 = Vector.add(this.waves[i].position, Vector.mult(this.waves[i].unit1, this.waves[i].radius)) + const v2 = Vector.add(this.waves[i].position, Vector.mult(this.waves[i].unit2, this.waves[i].radius)) + //draw wave + ctx.moveTo(v1.x, v1.y) + ctx.arc(this.waves[i].position.x, this.waves[i].position.y, this.waves[i].radius, this.waves[i].angle, this.waves[i].angle + this.waves[i].arc); + //using small angle linear approximation of circle arc, this will not work if the arc gets large // https://stackoverflow.com/questions/13652518/efficiently-find-points-inside-a-circle-sector + let hits = Matter.Query.ray([player], v1, v2, 50) //Matter.Query.ray(bodies, startPoint, endPoint, [rayWidth]) + for (let j = 0, len = Math.min(30, hits.length); j < len; j++) { + player.force.x += 0.01 * (Math.random() - 0.5) * player.mass + player.force.y += (0.01 * (Math.random() - 0.5) - simulation.g * 0.25) * player.mass //remove force of gravity + Matter.Body.setVelocity(player, { //friction + x: player.velocity.x * 0.95, + y: player.velocity.y * 0.95 + }); + m.damage(damage) + } + hits = Matter.Query.ray(body, v1, v2, 50) + for (let j = 0, len = Math.min(30, hits.length); j < len; j++) { + const who = hits[j].body + //make them shake around + who.force.x += 0.01 * (Math.random() - 0.5) * who.mass + who.force.y += (0.01 * (Math.random() - 0.5) - simulation.g * 0.25) * who.mass //remove force of gravity + + let vertices = who.vertices; + const vibe = 25 + ctx.moveTo(vertices[0].x + vibe * (Math.random() - 0.5), vertices[0].y + vibe * (Math.random() - 0.5)); + for (let j = 1; j < vertices.length; j++) { + ctx.lineTo(vertices[j].x + vibe * (Math.random() - 0.5), vertices[j].y + vibe * (Math.random() - 0.5)); + } + ctx.lineTo(vertices[0].x + vibe * (Math.random() - 0.5), vertices[0].y + vibe * (Math.random() - 0.5)); + + if (tech.isPhononBlock && !who.isNotHoldable && who.speed < 5 && who.angularSpeed < 0.1) { + if (Math.random() < 0.5) b.targetedBlock(who, 50 - Math.min(25, who.mass * 3)) // targetedBlock(who, speed = 50 - Math.min(20, who.mass * 2), range = 1600) { + // Matter.Body.setAngularVelocity(who, (0.25 + 0.12 * Math.random()) * (Math.random() < 0.5 ? -1 : 1)); + who.torque += who.inertia * 0.001 * (Math.random() - 0.5) + } + } + // ctx.stroke(); //draw vibes + this.waves[i].radius += tech.waveBeamSpeed * 1.8 * this.waves[i].expanding //expand / move + if (this.waves[i].radius > end - 30) { + this.waves[i].expanding = -1 + if (this.waves[i].reflection < 1) this.waves.splice(i, 1) //end + } else if (this.waves[i].radius < 25) { + this.waves[i].expanding = 1 + if (this.waves[i].reflection < 1) this.waves.splice(i, 1) //end + } + } + ctx.stroke(); + } + } + me.lasers = function (where, angle) { + const vertexCollision = function (v1, v1End, domain) { + for (let i = 0; i < domain.length; ++i) { + let vertices = domain[i].vertices; + const len = vertices.length - 1; + for (let j = 0; j < len; j++) { + results = simulation.checkLineIntersection(v1, v1End, vertices[j], vertices[j + 1]); + if (results.onLine1 && results.onLine2) { + const dx = v1.x - results.x; + const dy = v1.y - results.y; + const dist2 = dx * dx + dy * dy; + if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) best = { + x: results.x, + y: results.y, + dist2: dist2, + who: domain[i], + v1: vertices[j], + v2: vertices[j + 1] + }; + } + } + results = simulation.checkLineIntersection(v1, v1End, vertices[0], vertices[len]); + if (results.onLine1 && results.onLine2) { + const dx = v1.x - results.x; + const dy = v1.y - results.y; + const dist2 = dx * dx + dy * dy; + if (dist2 < best.dist2) best = { + x: results.x, + y: results.y, + dist2: dist2, + who: domain[i], + v1: vertices[0], + v2: vertices[len] + }; + } + } + }; + + const seeRange = 7000; + best = { + x: null, + y: null, + dist2: Infinity, + who: null, + v1: null, + v2: null + }; + const look = { + x: where.x + seeRange * Math.cos(angle), + y: where.y + seeRange * Math.sin(angle) + }; + // vertexCollision(where, look, mob); + vertexCollision(where, look, map); + vertexCollision(where, look, body); + if (!m.isCloak) vertexCollision(where, look, [player]); + if (best.who && (best.who === player) && m.immuneCycle < m.cycle) { + const dmg = 0.0011 * simulation.dmgScale; + m.damage(dmg); + simulation.drawList.push({ //add dmg to draw queue + x: best.x, + y: best.y, + radius: dmg * 1500, + color: "rgba(80,0,255,0.5)", + time: 20 + }); + } + //draw beam + if (best.dist2 === Infinity) best = look; + ctx.moveTo(where.x, where.y); + ctx.lineTo(best.x, best.y); + ctx.lineWidth = 10; + ctx.stroke(); + } + me.pulse = function (charge, angle, where = this.position) { + let best; + angle = Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x) + let explosionRadius = 5.5 * charge + let range = 5000 + const path = [{ + x: where.x + 20 * Math.cos(angle), + y: where.y + 20 * Math.sin(angle) + }, + { + x: where.x + range * Math.cos(angle), + y: where.y + range * Math.sin(angle) + } + ]; + const vertexCollision = function (v1, v1End, domain) { + for (let i = 0; i < domain.length; ++i) { + let vertices = domain[i].vertices; + const len = vertices.length - 1; + for (let j = 0; j < len; j++) { + results = simulation.checkLineIntersection(v1, v1End, vertices[j], vertices[j + 1]); + if (results.onLine1 && results.onLine2) { + const dx = v1.x - results.x; + const dy = v1.y - results.y; + const dist2 = dx * dx + dy * dy; + if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) best = { + x: results.x, + y: results.y, + dist2: dist2, + who: domain[i], + v1: vertices[j], + v2: vertices[j + 1] + }; + } + } + results = simulation.checkLineIntersection(v1, v1End, vertices[0], vertices[len]); + if (results.onLine1 && results.onLine2) { + const dx = v1.x - results.x; + const dy = v1.y - results.y; + const dist2 = dx * dx + dy * dy; + if (dist2 < best.dist2) best = { + x: results.x, + y: results.y, + dist2: dist2, + who: domain[i], + v1: vertices[0], + v2: vertices[len] + }; + } + } + }; + //check for collisions + best = { + x: null, + y: null, + dist2: Infinity, + who: null, + v1: null, + v2: null + }; + if (!best.who) { + vertexCollision(path[0], path[1], body); + vertexCollision(path[0], path[1], [player]); + vertexCollision(path[0], path[1], map); + if (best.dist2 != Infinity) { //if hitting something + path[path.length - 1] = { + x: best.x, + y: best.y + }; + } + } + if (best.who) { + b.explosion(path[1], explosionRadius, "rgba(0,0,0,0)") + const off = explosionRadius * 1.2 + b.explosion({ x: path[1].x + off * (Math.random() - 0.5), y: path[1].y + off * (Math.random() - 0.5) }, explosionRadius, "rgba(0,0,0,0.7)") + b.explosion({ x: path[1].x + off * (Math.random() - 0.5), y: path[1].y + off * (Math.random() - 0.5) }, explosionRadius, "rgba(0,0,0,0.7)") + } + //draw laser beam + ctx.beginPath(); + ctx.moveTo(path[0].x, path[0].y); + ctx.lineTo(path[1].x, path[1].y); + if (charge > 50) { + ctx.strokeStyle = "rgba(0,0,0,0.10)" + ctx.lineWidth = 70 + ctx.stroke(); + } + ctx.strokeStyle = "rgba(0,0,0,0.25)" + ctx.lineWidth = 20 + ctx.stroke(); + ctx.strokeStyle = "#f00"; + ctx.lineWidth = 4 + ctx.stroke(); + + //draw little dots along the laser path + const sub = Vector.sub(path[1], path[0]) + const mag = Vector.magnitude(sub) + for (let i = 0, len = Math.floor(mag * 0.0005 * charge); i < len; i++) { + const dist = Math.random() + simulation.drawList.push({ + x: path[0].x + sub.x * dist + 10 * (Math.random() - 0.5), + y: path[0].y + sub.y * dist + 10 * (Math.random() - 0.5), + radius: 1.5 + 5 * Math.random(), + color: "rgba(0,0,0,0.5)", + time: Math.floor(9 + 25 * Math.random() * Math.random()) + }); + } + } + let c = 0 + me.gun = function () { + if (b.activeGun == 0) {// nailgun + if (this.seePlayer.recall && !(simulation.cycle % 20)) { + this.fireDir = Vector.normalise(Vector.sub(this.seePlayer.position, this.position)); + normalBullet(this.position.x, this.position.y); + const v = 10 + 8 * simulation.accelScale; + Matter.Body.setVelocity(mob[mob.length - 1], { + x: this.velocity.x + this.fireDir.x * v, + y: this.velocity.y + this.fireDir.y * v + }); + } + } + if (b.activeGun == 1) {// shotgun + if (this.seePlayer.recall && !(simulation.cycle % 90)) { + const side = 22 + for (let i = 0; i < 12; i++) { + const angle = Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x); + const dir = angle + (Math.random() - 0.5) * 1 + const SPEED = 52 + Math.random() * 8 + normalBullet(this.position.x + 35 * Math.cos(angle) + 15 * (Math.random() - 0.5), this.position.y + 35 * Math.sin(angle) + 15 * (Math.random() - 0.5)) + Matter.Body.setVelocity(mob[mob.length - 1], { + x: SPEED * Math.cos(dir), + y: SPEED * Math.sin(dir) + }); + } + } + } else if (b.activeGun == 2) { // super balls + if (this.seePlayer.recall && !(simulation.cycle % 20)) { + const num = 3; + const SPREAD = 0.13; + const angle = Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x); + let dir = angle - SPREAD * (num - 1) / 2; + const SPEED = 33 + for (let i = 0; i < num; i++) { + ball(this.position.x + 30 * Math.cos(angle), this.position.y + 30 * Math.sin(angle)) + Matter.Body.setVelocity(mob[mob.length - 1], { + x: SPEED * Math.cos(dir), + y: SPEED * Math.sin(dir) + }); + dir += SPREAD + } + } + } else if (b.activeGun == 3) { // wave + this.doLongitudinal() + const halfArc = 0.275 + const anglex = Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x); + const angle = anglex + 0.3 * (Math.random() - 0.5) + this.waves.push({ + position: { + x: this.position.x + 25 * Math.cos(anglex), + y: this.position.y + 25 * Math.sin(anglex), + }, + angle: angle - halfArc, //used in drawing ctx.arc + unit1: { x: Math.cos(angle - halfArc), y: Math.sin(angle - halfArc) }, //used for collision + unit2: { x: Math.cos(angle + halfArc), y: Math.sin(angle + halfArc) }, //used for collision + arc: halfArc * 2, + radius: 25, + reflection: 0, + expanding: 1, + resonanceCount: 0 + }) + } else if (b.activeGun == 4) { // missiles + + } else if (b.activeGun == 5) { // grenades + + } else if (b.activeGun == 6) { // spores + if (this.seePlayer.recall && !(simulation.cycle % 30)) { + this.fireDir = Vector.normalise(Vector.sub(this.seePlayer.position, this.position)); + me.drop(this.position.x, this.position.y) + const v = 10 + 8 * simulation.accelScale + (Math.random() * 20 - Math.random() * 20); + Matter.Body.setVelocity(mob[mob.length - 1], { + x: this.velocity.x + this.fireDir.x * v, + y: this.velocity.y + this.fireDir.y * v + }); + } + } else if (b.activeGun == 7) { // drones + + } else if (b.activeGun == 8) { // foam + if (this.seePlayer.recall && !(simulation.cycle % 1)) { + this.fireDir = Vector.normalise(Vector.sub(this.seePlayer.position, this.position)); + foamBullet(this.position.x, this.position.y, 7 + Math.ceil(this.radius / 15), 69); + const v = 10 + 8 * simulation.accelScale + (Math.random() * 20 - Math.random() * 20); + Matter.Body.setVelocity(mob[mob.length - 1], { + x: this.velocity.x + this.fireDir.x * v, + y: this.velocity.y + this.fireDir.y * v + }); + } + } else if (b.activeGun == 9) { // harpoon - railgun + if (c > 1) { + this.fireDir = Vector.normalise(Vector.sub(this.seePlayer.position, this.position)); + this.railBullet(this.position.x, this.position.y); + const v = 10 + 80 * simulation.accelScale; + Matter.Body.setVelocity(mob[mob.length - 1], { + x: this.velocity.x + this.fireDir.x * v, + y: this.velocity.y + this.fireDir.y * v + }); + c = 0; + } else { + c += 0.02; + this.railGun(); + } + } else if (b.activeGun == 10) { // laserMines + if (this.seePlayer.recall && !(simulation.cycle % 100)) { + this.fireDir = Vector.normalise(Vector.sub(this.seePlayer.position, this.position)); + me.laserMine(this.position.x, this.position.y) + const v = 10 + 8 * simulation.accelScale + (Math.random() * 20 - Math.random() * 20); + Matter.Body.setVelocity(mob[mob.length - 1], { + x: this.velocity.x + this.fireDir.x * v, + y: this.velocity.y + this.fireDir.y * v + }); + } + } else if (b.activeGun == 11) { // laser - pulse + //this.lasers(this.position, Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x)) + //if (this.seePlayer.recall && !(simulation.cycle % 20)) { + if (c > 1) { + this.pulse(c * 100) + c = 0; + } else { + if (this.energy < 1 || this.energy > 0.5) { + c += 0.01; + ctx.beginPath(); + const mag = Math.sqrt(c) + ctx.arc(this.position.x, this.position.y, c * 30, 0, 2 * Math.PI) + ctx.fillStyle = '#000000' + ctx.strokeStyle = 'transparent' + ctx.fill(); + ctx.stroke(); + this.energy -= 0.01; + ctx.strokeStyle = "#000000"; + ctx.lineWidth = 1.5 + // ctx.globalAlpha = 1; + } else { + c = 0; + this.energy += 0.1 + } + } + //} + } + } + me.railBullet = function (x, y) { + mobs.spawn(x, y, 5, 20, "black"); + let xy = mob[mob.length - 1]; + xy.stroke = "black"; + xy.vertices = Matter.Vertices.rotate(xy.vertices, Math.PI, xy.position); + xy.onHit = function () { + simulation.drawList.push({ //add dmg to draw queue + x: this.position.x, + y: this.position.y, + radius: 20, + color: simulation.mobDmgColor, + time: simulation.drawTime + }); + }; + Matter.Body.setDensity(xy, 0.00004); //normal is 0.001 + xy.timeLeft = 220; + xy.frictionAir = -0.01; + xy.restitution = -1; + xy.leaveBody = false; + xy.isDropPowerUp = false; + //xy.inertia = Infinity; + xy.isBadTarget = true; + xy.isMobBullet = true; + xy.showHealthBar = false; + xy.collisionFilter.category = cat.mobBullet; + xy.collisionFilter.mask = cat.player; + let index = 0; + xy.do = function () { + this.timeLimit(); + this.alwaysSeePlayer() + const setNoseShape = () => { + const mag = this.radius + this.radius * 10; + const angle = Math.atan2(me.seePlayer.position.y - this.position.y, me.seePlayer.position.x - this.position.x); + this.vertices[2].x = this.position.x + Math.cos(this.angle) * mag; + this.vertices[2].y = this.position.y + Math.sin(this.angle) * mag; + this.vertices[4].x = this.position.x + Math.cos(this.angle) * mag; + this.vertices[4].y = this.position.y + Math.sin(this.angle) * mag; + this.vertices[0].x = this.position.x + Math.cos(this.angle) * mag; + this.vertices[0].y = this.position.y + Math.sin(this.angle) * mag; + Matter.Body.setAngle(this, angle); + }; + const spike = Vector.mult(Vector.normalise(Vector.sub(this.vertices[2], this.position)), radius * 1000) + const spike2 = Vector.mult(Vector.normalise(Vector.sub(this.vertices[4], this.position)), radius * 1000) + const spike3 = Vector.mult(Vector.normalise(Vector.sub(this.vertices[0], this.position)), radius * 1000) + this.vertices[2].x = this.position.x + spike.x / 100 + this.vertices[2].y = this.position.y + spike.y / 100 + this.vertices[4].x = this.position.x + spike2.x / 75 + this.vertices[4].y = this.position.y + spike2.y / 75 + this.vertices[0].x = this.position.x + spike3.x / 75 + this.vertices[0].y = this.position.y + spike3.y / 75 + if (index == 0) { + setNoseShape(); + index++; + } + if (Matter.Query.collides(this, map).length > 0 || Matter.Query.collides(this, body).length > 0) { + const slow = 0.69 //im sorry it looks cool though + Matter.Body.setVelocity(this, { + x: this.velocity.x * slow, + y: this.velocity.y * slow + }); + // simulation.drawList.push({ //add dmg to draw queue + // x: this.position.x, + // y: this.position.y, + // radius: 10, + // color: '#000000', + // time: simulation.drawTime + // }); + if (this.velocity.x == 0 && this.velocity.y == 0) { + this.death(); + } + this.frictionAir += 0.0001; + Matter.Body.setAngularVelocity(this, 0) + } + if (Matter.Query.collides(this, [player]).length > 0 && !(m.isCloak && tech.isIntangible) && m.immuneCycle < m.cycle) { + m.immuneCycle = m.cycle + m.collisionImmuneCycles; //player is immune to damage for 30 cycles // I wasnt gonna add this but since ya'll would have killed me if I didn't I added this + const dmg = 0.013 * simulation.dmgScale; + m.damage(dmg); + // simulation.drawList.push({ //add dmg to draw queue + // x: this.position.x, + // y: this.position.y, + // radius: Math.sqrt(dmg) * 200, + // color: '#000000', + // time: simulation.drawTime + // }); + } + }; + } + me.laserMine = function (x, y) { + mobs.spawn(x, y, 3, 20, "#000000"); + let xx = mob[mob.length - 1]; + xx.stroke = "#00000000"; + Matter.Body.setDensity(xx, 0.000005) //one tap + xx.isUnstable = true; + xx.timeLeft = 40 + Math.floor(180 * Math.random()) + xx.leaveBody = false; + xx.isDropPowerUp = false; + xx.collisionFilter.mask = cat.bullet | cat.player + xx.showHealthBar = false; + //xx.vertices = Matter.Vertices.rotate(xx.vertices, Math.PI, xx.position); + me.onHit = function () { + this.death(); + }; + xx.do = function () { + this.timeLimit(); + Matter.Body.setAngularVelocity(this, 0.01) + ctx.beginPath(); + ctx.lineWidth = 1; + ctx.strokeStyle = "#00000000" + for (let i = 0; i < this.vertices.length; i++) { + const where = this.vertices[i] + const endPoint = Vector.add(where, Vector.mult(Vector.normalise(Vector.sub(where, this.position)), 2500)) + me.lasers(this.vertices[0], this.angle + Math.PI / 3); + me.lasers(this.vertices[1], this.angle + Math.PI); + me.lasers(this.vertices[2], this.angle - Math.PI / 3); + } + ctx.strokeStyle = randomColor({ + hue: "#FF00FF" + }); + ctx.stroke(); + ctx.save() + ctx.beginPath(); + ctx.moveTo(this.vertices[0].x, this.vertices[0].y); + ctx.lineTo(this.vertices[1].x, this.vertices[1].y); + ctx.lineTo(this.vertices[2].x, this.vertices[2].y); + ctx.fillStyle = "#000000"; + ctx.strokeStyle = "transparent"; + ctx.fill(); + ctx.closePath(); + ctx.stroke(); + ctx.restore() + } + } + me.seeker = function (x, y) { + mobs.spawn(x, y, sides = 5, radius = 5, "rgb(0,0,0)"); + let yy = mob[mob.length - 1]; + yy.stroke = "transparent"; + yy.onHit = function () { + this.explode(this.mass * 20); + }; + Matter.Body.setDensity(yy, 0.000015); //normal is 0.001 + yy.timeLeft = 420 //* (0.8 + 0.4 * Math.random()); + yy.accelMag = 0.00017 * simulation.accelScale; //* (0.8 + 0.4 * Math.random()) + yy.frictionAir = 0.01 //* (0.8 + 0.4 * Math.random()); + yy.restitution = 0.5; + yy.leaveBody = false; + yy.isDropPowerUp = false; + yy.isBadTarget = true; + yy.isMobBullet = true; + yy.showHealthBar = false; + yy.collisionFilter.category = cat.mobBullet; + yy.collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet; + let index = 0; + yy.do = function () { + this.alwaysSeePlayer() + this.timeLimit(); + this.attraction(); + }; + } + me.drop = function (x, y) { + mobs.spawn(x, y, sides = 90, radius = 30, "rgb(0,255,100,0.7)"); + let yyy = mob[mob.length - 1]; + yyy.stroke = "transparent"; + yyy.onDeath = function () { + for (let i = 0, len = 5; i < len; i++) { + me.seeker(this.position.x, this.position.y) + Matter.Body.setVelocity(mob[mob.length - 1], { + x: Math.random() * 30 - Math.random() * 30, + y: Math.random() * 30 - Math.random() * 30 + }); + } + }; + Matter.Body.setDensity(yyy, 0.000015); //normal is 0.001 + yyy.timeLeft = 60 //* (0.8 + 0.4 * Math.random()); + yyy.frictionAir = 0.01 //* (0.8 + 0.4 * Math.random()); + yyy.restitution = 0.5; + yyy.leaveBody = false; + yyy.isDropPowerUp = false; + yyy.isBadTarget = true; + yyy.isMobBullet = true; + yyy.showHealthBar = false; + yyy.collisionFilter.category = cat.mobBullet; + yyy.collisionFilter.mask = null; + yyy.maxRadius = 30; + let index = 0; + yyy.do = function () { + if (Matter.Query.collides(this, [player]).length > 0 && !(m.isCloak && tech.isIntangible) && m.immuneCycle < m.cycle) { + Matter.Body.setPosition(this, player.position) + if (player.speed > 2.5) Matter.Body.setVelocity(player, Vector.mult(player.velocity, 0.94)) + } + this.alwaysSeePlayer() + this.timeLimit(); + ctx.save() + ctx.beginPath(); + ctx.moveTo(this.position.x, this.position.y) + ctx.fillStyle = "black"; + ctx.arc(this.position.x, this.position.y, this.maxRadius, 0, 2 * Math.PI) + ctx.stroke() + ctx.fill() + ctx.restore() + if (this.maxRadius > 0) { + this.maxRadius -= 0.5; + } + }; + } + }; + restoreBoss(-13350, -1800); + laserEM(-6500 + Math.floor(Math.random() * 200) - Math.floor(Math.random() * 200), -3400 + Math.floor(Math.random() * 200) - Math.floor(Math.random() * 200)); + sniper(-9275 + Math.floor(Math.random() * 200) - Math.floor(Math.random() * 200), -3325 + Math.floor(Math.random() * 200) - Math.floor(Math.random() * 200)); + laserEM(-5750 + Math.floor(Math.random() * 200) - Math.floor(Math.random() * 200), -850 + Math.floor(Math.random() * 200) - Math.floor(Math.random() * 200)); + sniper(-3600 + Math.floor(Math.random() * 200) - Math.floor(Math.random() * 200), -1325 + Math.floor(Math.random() * 200) - Math.floor(Math.random() * 200)); + laserEM(1425 + Math.floor(Math.random() * 200) - Math.floor(Math.random() * 200), -800 + Math.floor(Math.random() * 200) - Math.floor(Math.random() * 200)); + //restoreBoss(-350, -3225); + wire(); + wire(); + wire(); + wire(); + wire(); + color.map = '#00000000'; + level.customTopLayer = () => { + if (dong.position.x > -3825) { + dong.force.y -= dong.mass * simulation.g; + } else { + dong.force.y += dong.mass * simulation.g; + } + Matter.Body.setAngularVelocity(dong, -0.5) + if (destroyed == false) { + door.isClosing = false; + } else { + door.isClosing = true; + } + door.openClose(); + door.draw() + for (let i = 0, len = map.length; i < len; i++) { //so boss bar renders over the map + // ctx.beginPath() + // //ctx.setLineDash([125 * Math.random(), 125 * Math.random()]); + // ctx.fillRect(map[i].vertices[0].x , map[i].vertices[0].y, Math.abs(map[i].vertices[0].x - map[i].vertices[1].x), Math.abs(map[i].vertices[1].y - map[i].vertices[2].y)) + // //ctx.strokeRect(map[i].vertices[0].x , map[i].vertices[0].y, Math.abs(map[i].vertices[0].x - map[i].vertices[1].x), Math.abs(map[i].vertices[1].y - map[i].vertices[2].y)) + // ctx.fillStyle = "rgba(68,68,68)" + // //ctx.strokeStyle = "transparent" + // ctx.stroke() + // ctx.fill() + ctx.beginPath(); + ctx.moveTo(map[i].vertices[0].x, map[i].vertices[0].y); + for (let j = 0, length = map[i].vertices.length; j < length; j++) { + ctx.lineTo(map[i].vertices[j].x, map[i].vertices[j].y); + } + ctx.lineTo(map[i].vertices[0].x, map[i].vertices[0].y); + ctx.fillStyle = "rgba(68,68,68)"; + ctx.strokeStyle = "transparent"; + ctx.fill(); + ctx.stroke(); + // ctx.setLineDash([]); + } + for (let i = 0, len = mob.length; i < len; i++) { + if (mob[i].restoreBoss) { + ctx.save(); + ctx.setTransform(1, 0, 0.5, 1, 0, 0); //slanted + ctx.fillStyle = "rgba(100, 100, 100, 0.3)"; + ctx.fillRect(canvas.width2 / 2, canvas.height2 / 10, canvas.width2, 30); + ctx.fillStyle = "rgba(0,0,0,0.7)"; + ctx.fillRect(canvas.width2 / 2, canvas.height2 / 10, canvas.width2 * mob[i].health, 30); + ctx.restore(); + } + } + }; + simulation.enableConstructMode() //landgreen if you see this can you remove im probably gonna forget + for (let i = 0; i < spawn.bossTypeSpawnOrder.length * Math.random(); i++) { + spawn.bossTypeSpawnOrder.splice(i * Math.floor(Math.random() * spawn.bossTypeSpawnOrder.length), 1, "restoreBoss") //meh good enough + } + const obj = { restoreBoss }; + Object.assign(spawn, obj); //ez + }, + // ******************************************************************************************************** // ******************************************************************************************************** // ***************************************** training levels ********************************************** diff --git a/js/player.js b/js/player.js index 20c02b7..0f4eb86 100644 --- a/js/player.js +++ b/js/player.js @@ -1896,9 +1896,7 @@ const m = { } }, setMaxEnergy() { - // (m.fieldMode === 0 || m.fieldMode === 1) * 0.4 * m.coupling + - m.maxEnergy = (tech.isMaxEnergyTech ? 0.5 : 1) + tech.bonusEnergy + tech.healMaxEnergyBonus + tech.harmonicEnergy + 2 * tech.isGroundState + 3 * tech.isRelay * tech.isFlipFlopOn * tech.isRelayEnergy + 1.5 * (m.fieldMode === 1) - // if (tech.isEnergyHealth) m.maxEnergy *= Math.sqrt(m.defense()) + m.maxEnergy = (tech.isMaxEnergyTech ? 0.5 : 1) + tech.bonusEnergy + tech.healMaxEnergyBonus + tech.harmonicEnergy + 2 * tech.isGroundState + 3 * tech.isRelay * tech.isFlipFlopOn * tech.isRelayEnergy + 1.5 * (m.fieldMode === 1) + (m.fieldMode === 0 || m.fieldMode === 1) * 0.5 * m.coupling + 0.4 * tech.isStandingWaveExpand simulation.makeTextLog(`m.maxEnergy = ${(m.maxEnergy.toFixed(2))}`) }, fieldMeterColor: "#0cf", @@ -2268,21 +2266,22 @@ const m = { } } }, + minEnergyToDeflect: 0.05, pushMass(who, fieldBlockCost = (0.025 + Math.sqrt(who.mass) * Vector.magnitude(Vector.sub(who.velocity, player.velocity)) * 0.002) * m.fieldShieldingScale) { - if (m.energy > fieldBlockCost * 0.2) { //shield needs at least some of the cost to block + if (m.energy > m.minEnergyToDeflect) { //shield needs at least some of the cost to block + if (who.isShielded) fieldBlockCost *= 1.5; //shielded mobs take more energy to block m.energy -= fieldBlockCost - if (m.energy < 0) { + if (m.energy < m.minEnergyToDeflect) { m.energy = 0; m.fieldCDcycle = m.cycle + Math.max(m.fieldBlockCD, 60); if (tech.isLaserField) { simulation.ephemera.push({ name: "laser field", //used to find this array element in simulation.removeEphemera() - // tech.laserDrain = 0.0018; - count: Math.floor(m.maxEnergy * 30) * 0.0018 / tech.laserDrain, //how many cycles the ephemera lasts, scales with max energy + count: 15 + Math.floor(m.maxEnergy * 30 * 0.0018 / tech.laserDrain), //how many cycles the ephemera lasts, scales with max energy do() { this.count-- if (this.count < 0) simulation.removeEphemera(this.name) - for (let i = 0, num = 12; i < num; i++) { //draw random lasers + for (let i = 0, num = 20; i < num; i++) { //draw random lasers const angle = 6.28 * i / num + m.cycle * 0.04 b.laser({ x: m.pos.x + 30 * Math.cos(angle), y: m.pos.y + 30 * Math.sin(angle) }, { x: m.pos.x + 3000 * Math.cos(angle), y: m.pos.y + 3000 * Math.sin(angle) })//dmg = tech.laserDamage, reflections = tech.laserReflections, isThickBeam = false, push = 1 } @@ -2292,9 +2291,9 @@ const m = { } else { m.fieldCDcycle = m.cycle + m.fieldBlockCD; } - if (!who.isInvulnerable && (m.coupling && m.fieldMode < 3) && bullet.length < 250) { //for standing wave mostly + if (!who.isInvulnerable && (m.coupling && m.fieldMode === 0) && bullet.length < 200) { //for field emitter iceIX for (let i = 0; i < m.coupling; i++) { - if (m.coupling - i > Math.random()) { + if (m.coupling - i > 1.25 * Math.random()) { const sub = Vector.mult(Vector.normalise(Vector.sub(who.position, m.pos)), (m.fieldRange * m.harmonicRadius) * (0.4 + 0.3 * Math.random())) //m.harmonicRadius should be 1 unless you are standing wave expansion const rad = Vector.rotate(sub, 1 * (Math.random() - 0.5)) const angle = Math.atan2(sub.y, sub.x) @@ -2361,9 +2360,8 @@ const m = { ) { mob[i].locatePlayer(); m.pushMass(mob[i]); - if (mob[i].isShielded) { - m.fieldCDcycle = m.cycle + 30 - } else if (tech.deflectEnergy && !mob[i].isInvulnerable) { + + if (tech.deflectEnergy && !mob[i].isInvulnerable && !mob[i].isShielded) { m.energy += tech.deflectEnergy } } @@ -2464,8 +2462,9 @@ const m = { case 0: //field emitter return `gain the coupling effects of all fields` case 1: //standing wave - return `deflecting condenses +${couple.toFixed(1)} ice IX` - // return `+${couple.toFixed(1)} damage per max energy` + // return `deflecting condenses +${couple.toFixed(1)} ice IX` + // return `deflecting condenses +${couple.toFixed(1)} ice IX` + return `+${(couple * 50).toFixed(0)} maximum energy` case 2: //perfect diamagnetism return `deflecting condenses +${couple.toFixed(1)} ice IX` // return `invulnerable +${2*couple} seconds post collision` @@ -2501,7 +2500,7 @@ const m = { m.coupling = 0 //can't go negative } - // m.setMaxEnergy(); + m.setMaxEnergy(); // m.setMaxHealth(); m.setFieldRegen() mobs.setMobSpawnHealth(); @@ -2568,7 +2567,7 @@ const m = { if (m.energy > m.fieldRegen) m.energy -= m.fieldRegen m.grabPowerUp(); m.lookForPickUp(); - if (m.energy > 0) { + if (m.energy > m.minEnergyToDeflect) { m.drawField(); m.pushMobsFacing(); } @@ -2592,7 +2591,7 @@ const m = { m.fieldBlockCD = 0; m.blockingRecoil = 2 //4 is normal m.fieldRange = 185 - m.fieldShieldingScale = 1.6 * Math.pow(0.6, (tech.harmonics - 2)) + m.fieldShieldingScale = 1.6 * Math.pow(0.5, (tech.harmonics - 2)) // m.fieldHarmReduction = 0.66; //33% reduction m.harmonic3Phase = () => { //normal standard 3 different 2-d circles @@ -2620,7 +2619,6 @@ const m = { m.pushMass(mob[i]); this.drainCD = m.cycle + 15 } - if (mob[i].isShielded || mob[i].shield) m.fieldCDcycle = m.cycle + 10 } } } @@ -2671,7 +2669,7 @@ const m = { } else { 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) } - if (m.energy > 0 && m.fieldCDcycle < m.cycle) { + if (m.energy > m.minEnergyToDeflect && m.fieldCDcycle < m.cycle) { if (tech.isStandingWaveExpand) { if (input.field) { // const oldHarmonicRadius = m.harmonicRadius @@ -2719,7 +2717,7 @@ const m = { ) { mob[i].locatePlayer(); const unit = Vector.normalise(Vector.sub(m.fieldPosition, mob[i].position)) - m.fieldCDcycle = m.cycle + m.fieldBlockCD + (mob[i].isShielded ? 18 : 0); + m.fieldCDcycle = m.cycle + m.fieldBlockCD + (mob[i].isShielded ? 10 : 0); if (!mob[i].isInvulnerable && bullet.length < 250) { for (let i = 0; i < m.coupling; i++) { if (m.coupling - i > Math.random()) { @@ -3207,7 +3205,7 @@ const m = { if (m.energy > m.fieldRegen) m.energy -= m.fieldRegen m.grabPowerUp(); m.lookForPickUp(); - if (m.energy > 0) { + if (m.energy > m.minEnergyToDeflect) { m.drawField(); m.pushMobsFacing(); } diff --git a/js/tech.js b/js/tech.js index 92218ca..b6a613c 100644 --- a/js/tech.js +++ b/js/tech.js @@ -246,7 +246,7 @@ const tech = { if (tech.isBotDamage) dmg *= 1 + 0.06 * b.totalBots() if (tech.restDamage > 1 && player.speed < 1) dmg *= tech.restDamage if (tech.isLowEnergyDamage) dmg *= 1 + 0.7 * Math.max(0, 1 - m.energy) - if (tech.energyDamage) dmg *= 1 + m.energy * 0.15 * tech.energyDamage; + if (tech.energyDamage) dmg *= 1 + m.energy * 0.22 * tech.energyDamage; if (tech.isDamageFromBulletCount) dmg *= 1 + bullet.length * 0.007 if (tech.isNoFireDamage && m.cycle > m.fireCDcycle + 120) dmg *= 2 if (tech.isSpeedDamage) dmg *= 1 + Math.min(0.66, player.speed * 0.0165) @@ -2553,27 +2553,6 @@ const tech = { tech.isPiezo = false; } }, - { - name: "electronegativity", - descriptionFunction() { - return `+0.15% damage per current stored energy
(+${(15 * m.energy).toFixed(0)}%)` - }, - // description: "+1% damage per 8 stored energy", - maxCount: 9, - count: 0, - frequency: 1, - frequencyDefault: 1, - allowed() { - return true - }, - requires: "", - effect() { - tech.energyDamage++ - }, - remove() { - tech.energyDamage = 0; - } - }, { name: "ground state", description: "+200 maximum energy
–40% passive energy generation", @@ -4761,7 +4740,7 @@ const tech = { frequency: 2, frequencyDefault: 2, allowed() { - return tech.isIceCrystals || tech.isSporeFreeze || (m.fieldMode === 4 && simulation.molecularMode === 2) || tech.isIceShot || tech.relayIce || tech.isNeedleIce || (m.coupling && m.fieldMode < 3) + return tech.isIceCrystals || tech.isSporeFreeze || (m.fieldMode === 4 && simulation.molecularMode === 2) || tech.isIceShot || tech.relayIce || tech.isNeedleIce || (m.coupling && (m.fieldMode === 3 || m.fieldMode === 0)) }, requires: "a freeze effect", effect() { @@ -4780,7 +4759,7 @@ const tech = { frequency: 2, frequencyDefault: 2, allowed() { - return tech.isIceCrystals || tech.isSporeFreeze || (m.fieldMode === 4 && simulation.molecularMode === 2) || tech.isIceShot || tech.relayIce || tech.isNeedleIce || (m.coupling && m.fieldMode < 3) + return tech.isIceCrystals || tech.isSporeFreeze || (m.fieldMode === 4 && simulation.molecularMode === 2) || tech.isIceShot || tech.relayIce || tech.isNeedleIce || (m.coupling && (m.fieldMode === 3 || m.fieldMode === 0)) }, requires: "a freeze effect", effect() { @@ -4799,7 +4778,7 @@ const tech = { frequency: 2, frequencyDefault: 2, allowed() { - return (tech.isIceCrystals || tech.isSporeFreeze || (m.fieldMode === 4 && simulation.molecularMode === 2) || tech.isIceShot || tech.relayIce || tech.isNeedleIce || (m.coupling && m.fieldMode < 3)) && !tech.sporesOnDeath && !tech.isExplodeMob && !tech.botSpawner && !tech.isMobBlockFling && !tech.nailsDeathMob + return (tech.isIceCrystals || tech.isSporeFreeze || (m.fieldMode === 4 && simulation.molecularMode === 2) || tech.isIceShot || tech.relayIce || tech.isNeedleIce || (m.coupling && (m.fieldMode === 3 || m.fieldMode === 0))) && !tech.sporesOnDeath && !tech.isExplodeMob && !tech.botSpawner && !tech.isMobBlockFling && !tech.nailsDeathMob }, requires: "a localized freeze effect, no other mob death tech", effect() { @@ -4818,7 +4797,7 @@ const tech = { frequency: 2, frequencyDefault: 2, allowed() { - return (m.fieldMode === 4 && simulation.molecularMode === 2) || tech.relayIce || tech.isNeedleIce || (m.coupling && m.fieldMode < 3) || tech.iceIXOnDeath || tech.isIceShot + return (m.fieldMode === 4 && simulation.molecularMode === 2) || tech.relayIce || tech.isNeedleIce || (m.coupling && (m.fieldMode === 3 || m.fieldMode === 0)) || tech.iceIXOnDeath || tech.isIceShot }, requires: "ice IX", effect() { @@ -4837,7 +4816,7 @@ const tech = { frequency: 2, frequencyDefault: 2, allowed() { - return tech.isIceCrystals || tech.isSporeFreeze || (m.fieldMode === 4 && simulation.molecularMode === 2) || tech.relayIce || tech.isNeedleIce || (m.coupling && m.fieldMode < 3) || tech.iceIXOnDeath || tech.isIceShot + return tech.isIceCrystals || tech.isSporeFreeze || (m.fieldMode === 4 && simulation.molecularMode === 2) || tech.relayIce || tech.isNeedleIce || (m.coupling && (m.fieldMode === 3 || m.fieldMode === 0)) || tech.iceIXOnDeath || tech.isIceShot }, requires: "a localized freeze effect", effect() { @@ -7208,6 +7187,48 @@ const tech = { //************************************************** field //************************************************** tech //************************************************** + { + name: "spherical harmonics", + description: "+50% standing wave deflection efficiency", //standing wave oscillates in a 3rd dimension
+ isFieldTech: true, + maxCount: 9, + count: 0, + frequency: 2, + frequencyDefault: 2, + allowed() { + return m.fieldMode === 1 && !tech.isLaserField + }, + requires: "standing wave, not surface plasmons", + effect() { + tech.harmonics++ + m.fieldShieldingScale = 1.6 * Math.pow(0.5, (tech.harmonics - 2)) + m.harmonicShield = m.harmonicAtomic + }, + remove() { + tech.harmonics = 2 + m.fieldShieldingScale = 1.6 * Math.pow(0.5, (tech.harmonics - 2)) + m.harmonicShield = m.harmonic3Phase + } + }, + { + name: "surface plasmons", + description: "if deflecting drains all your energy
emit laser beams that scale with max energy", + isFieldTech: true, + maxCount: 1, + count: 0, + frequency: 2, + frequencyDefault: 2, + allowed() { + return (m.fieldMode === 4 && tech.deflectEnergy === 0) || (m.fieldMode === 1 && tech.harmonics === 2) || m.fieldMode === 0 + }, + requires: "molecular assembler, standing wave, field emitter, not electric generator", + effect() { + tech.isLaserField = true + }, + remove() { + tech.isLaserField = false + } + }, { name: "zero point energy", description: `use ${powerUps.orb.research(2)}
+100 maximum energy`, @@ -7233,51 +7254,9 @@ const tech = { if (this.count > 0) powerUps.research.changeRerolls(2) } }, - { - name: "surface plasmons", - description: "if deflecting a mob drains all your energy
emit laser beams in every direction", - isFieldTech: true, - maxCount: 1, - count: 0, - frequency: 2, - frequencyDefault: 2, - allowed() { - return m.fieldMode === 4 || m.fieldMode === 1 || m.fieldMode === 0 - }, - requires: "molecular assembler, standing wave, field emitter", - effect() { - tech.isLaserField = true - }, - remove() { - tech.isLaserField = false - } - }, - { - name: "spherical harmonics", - description: "+40% standing wave deflection efficiency
no longer deactivates on mob shields", //standing wave oscillates in a 3rd dimension
- isFieldTech: true, - maxCount: 9, - count: 0, - frequency: 3, - frequencyDefault: 3, - allowed() { - return m.fieldMode === 1 - }, - requires: "standing wave", - effect() { - tech.harmonics++ - m.fieldShieldingScale = 1.6 * Math.pow(0.6, (tech.harmonics - 2)) - m.harmonicShield = m.harmonicAtomic - }, - remove() { - tech.harmonics = 2 - m.fieldShieldingScale = 1.6 * Math.pow(0.6, (tech.harmonics - 2)) - m.harmonicShield = m.harmonic3Phase - } - }, { name: "expansion", - description: "using standing wave field expands its radius", + description: "using standing wave field expands its radius
+40 maximum energy", isFieldTech: true, maxCount: 1, count: 0, @@ -7289,37 +7268,36 @@ const tech = { requires: "standing wave", effect() { tech.isStandingWaveExpand = true + m.setMaxEnergy() // m.fieldShieldingScale = (tech.isStandingWaveExpand ? 0.9 : 1.6) * Math.pow(0.6, (tech.harmonics - 2)) }, remove() { tech.isStandingWaveExpand = false + m.setMaxEnergy() // m.fieldShieldingScale = (tech.isStandingWaveExpand ? 0.9 : 1.6) * Math.pow(0.6, (tech.harmonics - 2)) m.harmonicRadius = 1 } }, { - name: "triple point", + name: "electronegativity", descriptionFunction() { - return `+1.5 second ice IX freeze effect
spawn ${powerUps.orb.coupling(10)} that each give +0.1 coupling` //
${m.couplingDescription(1)} ${m.fieldMode === 0 ? "" : "per coupling"} + return `+0.22% damage per current stored energy
(up to +${(22 * m.maxEnergy).toFixed(0)}% damage at max energy)` }, + // description: "+1% damage per 8 stored energy", isFieldTech: true, - maxCount: 3, + maxCount: 9, count: 0, frequency: 2, frequencyDefault: 2, allowed() { - return m.fieldMode === 1 || m.fieldMode === 2 + return m.fieldMode === 1 || m.fieldMode === 9 || m.fieldMode === 8 }, - requires: "standing wave, perfect diamagnetism", + requires: "standing wave, wormhole, pilot wave", effect() { - tech.iceIXFreezeTime += 90 - powerUps.spawnDelay("coupling", 10) + tech.energyDamage++ }, remove() { - tech.iceIXFreezeTime = 150 - if (this.count) { - m.couplingChange(-this.count) - } + tech.energyDamage = 0; } }, { @@ -7379,6 +7357,29 @@ const tech = { tech.isStunField = 0; } }, + { + name: "triple point", + descriptionFunction() { + return `+1.5 second ice IX freeze effect
spawn ${powerUps.orb.coupling(10)} that each give +0.1 coupling` //
${m.couplingDescription(1)} ${m.fieldMode === 0 ? "" : "per coupling"} + }, + isFieldTech: true, + maxCount: 3, + count: 0, + frequency: 2, + frequencyDefault: 2, + allowed() { + return m.fieldMode === 2 + }, + requires: "perfect diamagnetism", + effect() { + tech.iceIXFreezeTime += 90 + powerUps.spawnDelay("coupling", 10) + }, + remove() { + tech.iceIXFreezeTime = 150 + if (this.count) m.couplingChange(-this.count) + } + }, { name: "eddy current brake", description: "perfect diamagnetism slows nearby mobs
effect radius scales with stored energy", @@ -7472,9 +7473,9 @@ const tech = { frequency: 2, frequencyDefault: 2, allowed() { - return (m.fieldMode === 8 || m.fieldMode === 3 || m.fieldMode === 1) && !tech.isCloakHealLastHit + return (m.fieldMode === 8 || m.fieldMode === 3) && !tech.isCloakHealLastHit }, - requires: "negative mass, pilot wave, standing wave, not patch", + requires: "negative mass, pilot wave, not patch", effect() { tech.lastHitDamage += 5; }, @@ -7796,9 +7797,9 @@ const tech = { frequency: 2, frequencyDefault: 2, allowed() { - return m.fieldMode === 4 + return m.fieldMode === 4 && !tech.isLaserField }, - requires: "molecular assembler", + requires: "molecular assembler, not surface plasmon", effect() { tech.deflectEnergy += 0.5; }, diff --git a/todo.txt b/todo.txt index 7d3d6e1..9abba39 100644 --- a/todo.txt +++ b/todo.txt @@ -1,25 +1,24 @@ ******************************************************** NEXT PATCH ************************************************** -tech - after deflecting drains all your energy, shoot lasers in every direction - is it too hard to safely lose all energy after deflecting - add in some mob knock back or damage immunity? - make this a tech? - balance damage - download image - sometimes the field goes on CD after deflection but when energy isn't 0 - fixed? - look at side effects of setting standing wave, molecular assembler, emitter to not disable at 0 energy, not 0.05 or 0.1 - might allow repeated deflection with no CD? - energy regen is disable when deflection is on CD - m.fieldCDcycle < m.cycle - -tech expansion no long gives deflection efficiency - retrocausality drains 30% less energy as time rewinds, but each time you start to rewind you drain 30 energy - no longer provides immunity for 1 second after on ending rewind - also spawns 20% fewer bots and grenades + no longer provides immunity for 1 second after exiting rewind + spawns 20% fewer bots -bug fixes +deflecting changes + shielded mobs take 50% more energy to deflect + deflecting shielded mobs now only disables your shield for perfect diamagnetism and not for very long + you can deflect any mob if you have at least 5% energy, but if deflecting brings you below that your shield is disabled for 1 second + also you can't passively regen energy while shield is disabled + +standing wave rework + coupling gives iceIX -> max energy + expansion gives deflection efficiency -> +50 max energy + spherical harmonics gives 40 -> 50% deflection efficiency + electronegativity is a fieldTech for standing wave, wormhole, and pilot wave + also 0.15 -> 0.22% damage per energy + dynamic equilibrium is no longer unlocked by standing wave + +tech: surface plasmons - after deflecting drains all your energy, shoot lasers in every direction added a generic system to add code that loops for a set time Example code: @@ -33,17 +32,19 @@ simulation.ephemera.push({ }, }) +some bug fixes +also new bugs probably + *********************************************************** TODO ***************************************************** +use ephemera to replace things + JUNK? + request animation stuff + tech: choose next map by name after exiting current map use modified tech selection code? this might be too much work without much reward - JUNK only? - - -build a system to add in methods to an array that runs these objects in the game loop - method to run the code - condition to remove from array should be inside the method + JUNK only? or maybe combine with other buff Tech: relativity Simulation speed scales with movement speed. When still, time moves at 0.4 speed, at full walking speed it’s 1. (So if you’re falling or something and you move faster the simulation will be faster than usual) @@ -1279,3 +1280,5 @@ if pause is pressed while selecting power ups, display pause menu on top of sele boost, coupling power ups tech - cyan electron orbiting a black nucleus electric field as bas-relief //(by Kazumasa Nagai) radioactive - volumetric atomic nucleus diagram by Paul Catherall + +