From 2820fcc29e91aac8155ffef45560070ecfc5752d Mon Sep 17 00:00:00 2001 From: danil <61111955+danilwhale@users.noreply.github.com> Date: Sun, 8 Dec 2024 09:52:09 +0200 Subject: [PATCH] [examples] improve input_virtual_controls example (#4584) --- examples/core/core_input_virtual_controls.c | 175 +++++++++++------- examples/core/core_input_virtual_controls.png | Bin 9094 -> 1802 bytes 2 files changed, 113 insertions(+), 62 deletions(-) diff --git a/examples/core/core_input_virtual_controls.c b/examples/core/core_input_virtual_controls.c index bbbad208f..76eeafee4 100644 --- a/examples/core/core_input_virtual_controls.c +++ b/examples/core/core_input_virtual_controls.c @@ -6,7 +6,8 @@ * * Example create by GreenSnakeLinux (@GreenSnakeLinux), * lighter by oblerion (@oblerion) and -* reviewed by Ramon Santamaria (@raysan5) +* reviewed by Ramon Santamaria (@raysan5) and +* improved by danilwhale (@danilwhale) * * Example licensed under an unmodified zlib/libpng license, which is an OSI-certified, * BSD-like license that allows static linking with closed source software @@ -17,6 +18,16 @@ #include "raylib.h" #include + +typedef enum { + BUTTON_NONE = -1, + BUTTON_UP, + BUTTON_LEFT, + BUTTON_RIGHT, + BUTTON_DOWN, + BUTTON_MAX +} PadButton; + //------------------------------------------------------------------------------------ // Program main entry point //------------------------------------------------------------------------------------ @@ -29,24 +40,38 @@ int main(void) InitWindow(screenWidth, screenHeight, "raylib [core] example - input virtual controls"); - const float dpadX = 90; - const float dpadY = 300; - const float dpadRad = 25.0f;//radius of each pad - Color dpadColor = BLUE; - int dpadKeydown = -1;//-1 if not down, else 0,1,2,3 + Vector2 padPosition = { 100, 350 }; + float buttonRadius = 30; - - const float dpadCollider[4][2]= // collider array with x,y position + Vector2 buttonPositions[BUTTON_MAX] = { - {dpadX,dpadY-dpadRad*1.5f},//up - {dpadX-dpadRad*1.5f,dpadY},//left - {dpadX+dpadRad*1.5f,dpadY},//right - {dpadX,dpadY+dpadRad*1.5f}//down + { padPosition.x,padPosition.y - buttonRadius*1.5f }, // Up + { padPosition.x - buttonRadius*1.5f, padPosition.y }, // Left + { padPosition.x + buttonRadius*1.5f, padPosition.y }, // Right + { padPosition.x, padPosition.y + buttonRadius*1.5f } // Down }; - const char dpadLabel[4]="XYBA";//label of Dpad - float playerX=100; - float playerY=100; + const char *buttonLabels[BUTTON_MAX] = + { + "Y", // Up + "X", // Left + "B", // Right + "A" // Down + }; + + Color buttonLabelColors[BUTTON_MAX] = + { + YELLOW, // Up + BLUE, // Left + RED, // Right + GREEN // Down + }; + + int pressedButton = BUTTON_NONE; + Vector2 inputPosition = { 0, 0 }; + + Vector2 playerPosition = { (float)screenWidth/2, (float)screenHeight/2 }; + float playerSpeed = 75; SetTargetFPS(60); //-------------------------------------------------------------------------------------- @@ -54,63 +79,89 @@ int main(void) // Main game loop while (!WindowShouldClose()) // Detect window close button or ESC key { - // Update - //-------------------------------------------------------------------------- - dpadKeydown = -1; //reset - int inputX = 0; - int inputY = 0; - if(GetTouchPointCount()>0) - {//use touch pos - inputX = GetTouchX(); - inputY = GetTouchY(); + // Update + //-------------------------------------------------------------------------- + if ((GetTouchPointCount() > 0)) + { + // Use touch position + inputPosition = GetTouchPosition(0); } else - {//use mouse pos - inputX = GetMouseX(); - inputY = GetMouseY(); - } - for(int i=0;i<4;i++) { - //test distance each collider and input < radius - if( fabsf(dpadCollider[i][1]-inputY) + fabsf(dpadCollider[i][0]-inputX) < dpadRad) - { - dpadKeydown = i; - break; - } + // Use mouse position + inputPosition = GetMousePosition(); } - // move player - switch(dpadKeydown){ - case 0: playerY -= 50*GetFrameTime(); - break; - case 1: playerX -= 50*GetFrameTime(); - break; - case 2: playerX += 50*GetFrameTime(); - break; - case 3: playerY += 50*GetFrameTime(); - default:; - }; - //-------------------------------------------------------------------------- - // Draw - //-------------------------------------------------------------------------- - BeginDrawing(); - ClearBackground(RAYWHITE); - for(int i=0;i<4;i++) + + // Reset pressed button to none + pressedButton = BUTTON_NONE; + + // Make sure user is pressing left mouse button if they're from desktop + if ((GetTouchPointCount() > 0) || ((GetTouchPointCount() == 0) && IsMouseButtonDown(MOUSE_BUTTON_LEFT))) + { + // Find nearest D-Pad button to the input position + for (int i = 0; i < BUTTON_MAX; i++) { - //draw all pad - DrawCircleV((Vector2) { dpadCollider[i][0], dpadCollider[i][1] }, dpadRad, dpadColor); - if(i!=dpadKeydown) + float distX = fabsf(buttonPositions[i].x - inputPosition.x); + float distY = fabsf(buttonPositions[i].y - inputPosition.y); + + if ((distX + distY < buttonRadius)) { - //draw label - DrawText(TextSubtext(dpadLabel,i,1), - (int)dpadCollider[i][0]-7, - (int)dpadCollider[i][1]-8,20,BLACK); + pressedButton = i; + break; } } + } + + // Move player according to pressed button + switch (pressedButton) + { + case BUTTON_UP: + { + playerPosition.y -= playerSpeed*GetFrameTime(); + break; + } + case BUTTON_LEFT: + { + playerPosition.x -= playerSpeed*GetFrameTime(); + break; + } + case BUTTON_RIGHT: + { + playerPosition.x += playerSpeed*GetFrameTime(); + break; + } + case BUTTON_DOWN: + { + playerPosition.y += playerSpeed*GetFrameTime(); + break; + } + default: break; + }; + + //-------------------------------------------------------------------------- + // Draw + //-------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground(RAYWHITE); + + // Draw world + DrawCircleV(playerPosition, 50, MAROON); + + // Draw GUI + for (int i = 0; i < BUTTON_MAX; i++) + { + DrawCircleV(buttonPositions[i], buttonRadius, (i == pressedButton)? DARKGRAY : BLACK); + + DrawText(buttonLabels[i], + (int)buttonPositions[i].x - 7, (int)buttonPositions[i].y - 8, + 20, buttonLabelColors[i]); + } + + DrawText("move the player with D-Pad buttons", 10, 10, 20, DARKGRAY); - DrawRectangleRec((Rectangle) { playerX - 4, playerY - 4, 75, 28 }, RED); - DrawText("Player", (int)playerX, (int)playerY, 20, WHITE); EndDrawing(); - //-------------------------------------------------------------------------- + //-------------------------------------------------------------------------- } // De-Initialization diff --git a/examples/core/core_input_virtual_controls.png b/examples/core/core_input_virtual_controls.png index cb1444598017b9d7ff83480f66909c8523ece519..83097a54ca46ebed8c1958e4ed7854cbbd2286a6 100644 GIT binary patch literal 1802 zcmeAS@N?(olHy`uVBq!ia0y~yU{+vYU_8Xa%)r3#)OW*g1_lPk;vjb?hIQv;UNSH+ zu%tWsIx;Y9?C1WI$jZRLAQ9jb;`;UL*MNY4eTwD`3=DsNGB8wre5Ps6@WdcSKHZps zfq}EYBeIx*f$tCqGm2_>HGnjhxJHx&=ckpFCl;kL1SDqWmFW4ohA5co8R{7(Z4KgK zU|`sm8sVAd>8ZuQ!N9=4${@wa%D~9Lz~IHez#t7}2Qe@(XfQH^#hDlw7z`Pi7z7v? z7@`;$7&6;gz~Wg95b&vtk>Le10|Sgkvy_3EfnfqW0}BHKgMpE;0pkLQsURC!7eGv! z0x^hziGh)U39K^6(t?4B0jkT;zyPG6X4aG6XOD*2Gcd4D^>lFzskrs_?%&+oZVbmB zGFvv9Y2-06hc95>qMg#Jcfo#Fo$%2zjk=eIMSfnZo2THVH2>v;x}A&HMudPYZt#En zrOx>Ove)nJtq#3xfB4>ivh%#W-#_9fD5c_o4W4#a`X#xzB2~ zE&p4|7*w?VTcvh|SiHj^C zzk`C3RNTOPb+c2;x_!Ub7Ho0k^JKcxd8jcfRK;^`7t706OzM-8uei1{Us>fbDNnb< zH@kjol#(Y%x*Wud2k~~jcGQ_@do`<5<1xa$Y;lL&WhA$E2mZ)jKAi_{=G^?B3E3IZY`q-*G3MuQGEy z#38io-pUU#O)Y{dJqnS#s+%oV+fP?WWZ_iykZ?S7Ww+=&&j|()S(qS)kQ-EpUHmYd zb9S#I*LhWtCJv!vmjw@jBuu`uYTC?JNQ|*i=uyag#~D;%2eR>)>c2}7AUhsAoyydi zV4!zQ-feqQNV5HW&FM_GC>9o}hke@EEXZ?8L3;Sf%NQX_HOQ!cXno`)*rp6Uz@T1NZYU3>HBo~ z(wyHuTUj3|bEfQ>|9$<>;s((&XVRjge}&f{j-7Vm*|dFX^Xm+YQVniL%zgY(OW?`R zTa!LVyNd_K$)91H_W$|gRD%akj~%SJ5nHTt;`7#2d6_fLEGIX8(=F7OmpbDNN(3{u zJ#%iZ>;U=9c*e#2_vYp-KY#m7So?LgW85@$|7MHT`42xTyf<&kfu@r)N3Y+zzb|_` zo8!)Mm) z^m|WdmKm8JU3*qvP~6~lqxYYsg@R{H^n|6KC!LD*usm4M79Aa}bad&l?`5VOyFV3) zCbDo|o@J}urI5&S@=>cQBn3As)c^T?*uF_-)mqlVu>1E7PT1$0H_5nzVogw`XL6N< zd_>0uRG zw%y^-m#UVHiAygk_sV5fJ?}T!D+4b_4m5lH@^$H+SPgg&ebxsLQ06xvk A$p8QV literal 9094 zcmeAS@N?(olHy`uVBq!ia0y~yU{+#aV7$x0#K6FiR}%Gxfq{XsILO_JVcj{ImkbOH zEa{HEjtmSN`?>!lvNA9*a29w(7BevL9R^{>S+Z*#dA9a!bnx!wYHliBmhGy% z@@-M+0t4sc>k7LvbdyRB=(V@}d#J_PGHhyYGHF5gQ)t`Fw8on@y+R?fHBz`2LT!$?=t+PO9%;WD;tX zu-@z2dpqH)b&tP@J$j@m5?U1IcilZ?d7*idC#zgVY+&nj`{19x&sVSA5SwKq-s{@` z;iuQy(7)-%x32lDE{&G03p{PGO=|siGjYe;-Nk*`rTJ#2yYsf}*d9CY&mz9u1@pc8 z^KWna_^`h6$;0;l-}-KE&%M1Z`FLNB+l{i0$oCb;t@U<(nbfMTZ}aNK;!P(l*XAu; z_kLPQd$`BR-=dekIzBah=ccpl0P}k7RkJ^F-*4X|_bvU{t8=c&FH5;g)`og^zS3&_ zv+i9uukh8#sb!^?v+jnq$6qn(S$k~t+Ow_~o?V*rIqL50wPzMp>%PmHuD*Z8W2tk` zFHQ5E{qNPa_5VKnzJGsLY4)6=2ZqOGHh;P6eZTUdwfLS4$2lRvNB&NDbi--p#@F$+ zihISPw#NnT4!^u=OJB5Rnfa$rs_hRSnTLcY2});doxgTj|E3*>d%tJ=X3jL;es#^Q zFL~E;H}e#GytUGJ)P7>lrN3)Uj(v~na+Z!M-TUIGzTM|J+ii9gKK}pvXYzTw-#Igk z-v4;qzyE=)_>r5H#Sgw^#J#tj+iUqF(Wt{&@5?!fJGo{83;#*`JHC~W<~Q?sxsz>I zl>7Z%e==6-npNAM`XqiXBG%enzWR;zx&0qn^=lHB2F?8b ztoYs>-+R9&FMlbSZECwb%-vHvd(Al)ujZXu*0CZCDi4=G^sp|xxX78$QibP?^}8L( zkC*(bOSm@oy@ge`e$&IZRX2(rAA9ZR7n4@;+k&U}div|f^NXimW-oQ^Kcste-(vpQ zYw>$3)8FgP*&nyd=1Jt^C9eAYhS`rgWBps|rTX9R{$7)R`kC%ckLX}`-@R||Y(F>K zTwmIM^@`Uki!E*Ee*Ra!KFsOz+v`)~qnEA7dg=Jd_qle`|K6h;PyI8U8o$K%?~%xp z<#iio$yEoG&&XZHx@*&a-{TBt=I$=7>HESgzUTEtcllh&&F_9*+pDe{Go$xSep1pd zvATUbW$O36{d07i`QD#bl>01_s#JC^@^g4zwClGF=hG+RZ;HRa3d`Tc?K}Nt@$SDK z`|NwS2I$t_S1a1Te?j5ywcpa0U3QPZYxjJ$uKnt(U##w4Is9g=_OGe?dv*ox_Db8O zWVyJ0ed*`Uix+>?EGg-T{Qu=J`+2L+zBfIhzu(*b?Z@OkyI(VsH+|f~Wy$!Pi5| zuh0Jf>i71V$M^LAezpI1^X>iLpXT1b`{6^f|NA=|OHV($@U|-J`1So?>z${s4QRP6 zHh)*);bRwME&r=#zmJw?J2d6hoPhTmZ=e5^8ZKw~|HFmz$98XuSNu8gve^E2Cw{iy zExIgz_ydnU_pJB#eCF=^aX;_Q21V(a*L-K*WZHh`OY4Rz8M%fHYd0LtNwGIx-QGQI zU-8@8@2SdbO|>_cbyfYj!`J@dMBP@~+pSeEw)-h`i3cZN-~7*eLDrGg8_MQo$FeIX z@BI|IKJL%0>sL$H*PYem{iwZm!p6GyllWT#ruK%nUNrc)O#lD&oIUDm<5#Q-QB&i) zv*GW`Uh6WOO|QlK{}ugC-aY@$-fI7)o0c2>HS-CpWtaO@BcpM_liR>vi9AH)#V?)l+IYc=CZi{J1fhrt4`J) zx2W26b9>fN2l-nu4(rw)*|q0*`ox$1OKQ?GN}G!D>G}76iT_+8Y8*D>#*HJU%8_TXY&M>MSEu)7xf=gmizhqZp8uQl+W7W18{5CL zAFCI!e{E8pH#7d$)ZQ~urRghAe7?4OSL}<5$Gi@?@@yN8ea|1#xN_*jmze*hr@zPe z<-0wuTa@wemE;*#lj$Y(`R7hmuiAC_O?3YGaKnvPq47KaJo5f@n^(JS=gkve=IP(qk?5Rz_s16lpFe-* z*Qu@9QFV2d!JcpXet+_3&OW^GkAZnvVOVhD#!G)#7k0agTwA-g^m*>~qp63=4j06{ z@xFR|dg7^L?ecbSrv%^IT=G&`+HB6gijSeEr?u`(43p43J70dc{@sxK-z2B?Z_a9E zUTgAqLdt`*`;{xw+5_*ujk@$oGx_L-8Sj^Et;vvhZg2bX%0c}*wI8_4*H@feWO3)%j$IcL1boJ=; zI3f4L*RtQ|uZ{e>^?3Q;N6F!Ff|o9x_N$4M;ESCh=XIhlt6nk1=GTey+ur_ieQ~bx zcJ$$l2y+n$HYZN`9bYbe*5CD^XY;u`+p^tX$VL2rkiPD;oORotPo4IEnhgwj{yaZl zXU9|X`Q886+0uU=+mjDCJA_-3|Gao#mvMXDa;NWScmKC@>y_M@cXWCF|J%C{ zz2X+%Yx5?3j)YNw&gV;~?Qg6)Bc1mzX~yoXvuhJK=3HWAZ(+Xk;lutT(~rH|@phZH z{{B7hv{&@oeLAr%*82a0;QyLyB%ZZBG(KmgJ$w89fVvsdyRz5q-*}5&qFQ<2x zwdv`|Z~HCnUH7!{>+45d4>hu0zLk#ue|m$@jMBSZFT0=rE9IZJCF3IV>+Aow?|#R% zSg~ks>9?CtV|O3gDzoYO|2@Co8OZE1T^;`XafpOn;lo1~ukVV-PVtp>-P*r=W}RQ_ z)6cKVcK+ITzT;X~RNGCJdFS4(SJ^df{l&a@Yxen^t#_QQ7rjE^cRkmU?B$u}^Q``C zTK%%&YtvnJd8u7{4YQNtQ};aTnBJ}@o%t|1T(6rMR5V9#4qN{#OM=%k_2DAd#3YHh z&A+aF*3bG>F52AME!Gwx%y#J0^tig4{qO4){MLV7yw!fsw_mR}Oh$P<>Gl7fb+7IB z{r|TiBBzXt^Re~)e`RKiwU$hOI-~fAjWrP8{5S1tuM8 z{c@tMSMl$2`wFKgT^luzY%1W&I{7~MdQ9>Cz2A>Le89VW{=coEtEFb|a0|NZzvu_& zH2W{hj{OYgIp+G$ZtdM$zPnd^|6TQZUHYw(n@-Kw{ysDbb2ZF5&z0@Zzge>Og}~-j zlG#=XvO>SweU7g=^~L)5MkTHD@9zEmayfQxzrb?4PrK#&)8+|HG==|9@>W zc>VVITL z_3=4pPpO@~@KOEE_pMh2E+(B0e(@?UXT2GRuYbSdVsmcsQjX)L)As$2t>0KxnsXuR zYS7idh`(Ec#jZzf-n7yr;%aYps;4<$of4M&(JJAay~&&ZkC!>`4K3Zw zvpIV8{MfB8*EJm3wdTRCq{Fs~A9rP3J*)Qf`^Hc2GgCKLU$si}mHBWw-tOlHU0d&=ilF~)VmvL`ogDsZRA18^tm7B=HC~d zbggy&)lZ3M+uF^KuxBll59@jB-23mYWd5$Qqoy}KuI}_c*R7wxTL0~6^u15pwjX<_ z5H~6DRPX%1HKhgfk99vfv-$k5Go|&Ddf${@-PFH6>hsngFAV%F-!0YW{d?od<)u5` z9pjZ=AN}#A`JB3{IT9@oudd!FWbEs=fBGBqegCExD!ln)dG!{@@BfRpUS8Xo82$ZD zZF%W4M)f%rNoyI`JX@00eKGX(x{CL|>vvl%+@}8fZP1FATY2rFNy4%FKHM@DaEg}6 z`10-Zx!<3}^Ut|$x)l+s{cV@1_NUTN^(p%^FE7hUSwDgMFy9q9(|<2ZukZcX^Gko@ zzkgH1*F|ksyMM~m`%9_kfu0usb2kj7(vH5pU#UL7KJVA>veL5m_vZ5QdOfy&@2a|X znxXcQ<>C83weat)J=uQm*Xrr>KfkxHICtsvy+7x|_kQ~E^Ru{2>6I%JC%enloH(eM zc6Rplt26mJvko8TKi=N9_1c;TkG%CcJy#!npU0uOcy7VFY_d0vK^q8&B zuDx>Jm~&e3Z;NJPRgJ}sSF+1yaveR!bhzM8ERX!oq)3m7#8a*BBdXp_QoXv=DZy6R zuV=BTVnXYNqPpALrzb6#xn>_zyl#!F#5z&A%~3TO601zsUDDXsA6m_7x902F-T(h> z2^KppdiX;pZ~dGF-*(oYyM6E5Z11|Kw%+$=_P+mm;MdpvJ5xMs&i@U*9y2+A!s>nQ z_O^dNb?fhY_^kG0-n0|{ugregzkgTh?#S9@vlGHzeacc;s(0Su&&t5>!Py6X?TYXU zcK`d?`E~s#fBSW><)XeP%$TZsW%|0Z^+79Reoo|nZX6~u`P%XSkKd<0ww+nA=U4Ot z>Dt$`zfV!~zUgsp&#LG1>-YWoQgG|p+1d9ip1tn3`t`!XN_qR8lFuL2W$r&Zy5`*U zbDvVf=aqh&9liJZ)XQ%}C8Q5HF=ZE~aqXVveBjo$Yu=mj3lii?!o^)M8}*vyLHv}8pYy0dB^Yc=!vtXe#zhU zt@_5<)X+DE*K#*HKUBPQ#9k$00e{Sgb!z)r?Q&G|p09mAuk_HNf16ciswry@^PR41Kjag;`_U{tryy(jJwGl<{uBF=>FfU^vpc^0ZI}Hiw^`qdjr#i~EO6(f8I5wUd~MH%ox8JQ*4uQonvC){lU@Zh zwx+L{HEnI=TG7qgpr+u7i1^6QSLe;%6xE^qOnXaeVBx}Ln`h6Rki=(mYHONkWRCXH zt!b{zH=f^8`(_%W`rEqg@$#!1%NNfsy?y=Rot4jTt~x9E>Yr1hgxjBOQY-Jye#sub zIPYCsS?aNCYi1r>(-@kRFMVo#=;{S!uC1+7>EYLVCV$>~=eFL~>5I-VO1#K>e$MgR z{oYqoZasNduC~s|`*n?*RR6|zUg!GFrmlXeb_-(3@{c{+_J>K>J}yk>eW3A5?}^;D z8)w&ESa;(YKkw~XvFY1a@8#vb8k$nNW!t+qR!>BnczZllecroSSXZA9=dU)kI&S2$ zk@**jPJG`r(I}I(^=xLd$utyM3O|`t#wt(qes)tvsyRQIC6tt9R(UaK8NO-7`L^ z4L>?T7XHt>93i#&+M0Xk|NeWva|8E^$&I-gVY65Ku5NXd$erWi_x4iOo@0lm+|#r9 zYWuH7h1YqHYIbbM-wD|_U46snh%W12^h+|%dfG-)+0CESb9F?Dw(`DNHMey8aeYKG|wr0s?wX%12 zB-){2$5~vyKbw`oVfzssh6WXy5lvIQmj3+wygQPCfz|r!mEeBMXA*|o3=1l_ z#dHKVGca(4tqgIBKf}bJF=GZR!-N(%ab%utHCV#K2_$=C<8e9ZUrAyN4hQE|KAU+# z9HjT#o6YCj_RU~r2-xs^UbWtrqdE)?O}794d|s%Z#>gOY=KWbF2C#Mrk#u^RE<|Qc zkSbV#vok|O3*7Hbl^Gli6DGi%5U^q8%9UV?CV^f0Otb=7HCmW8<=)+83U&dS1RA&L z=jDqG44yu|zO5jaBl#HNcT`ruhPBb#!QLcFF^Z$XE(a4_VJn3;GcbVJ2!bn2Z@M!m z_#q(#3TkXDjT!dam>DJ3@caGh(o76o zMUQ*U*F|sl%NApB5CX**8gX!2-d(9*`!a7h{+r!)+-{4RbXM-=>~kD{ccjS374FOA zU}*jQMBRR8>S?jHAZt1zjkmotO*eVF=_Uh1DA>SzXJ0M&cy~=0Z}`uhz24jE*RQ@B z6_|W-ZOMM0-tx^13kvRmV%{S=jD6+)%o{g?uI>m6JZ-Ynl$(KTxhXe;LMJGYuFtya z`2Bs_Q&T<3Dt-C)SlfJ$-8myMnFnfWS=$G5uUU97~y_v(1 zzyk6->*^4#h41gK;rmr-uHALI{J&qQwsGvT*?)QV%mx|ouub~f`uO!A%Q)9Y%07wS z+?MLLHrN=+4MjU;enoAzl1Rx8Gk+f|3JNcgbNP+z3?3kdFaD4h78C{YOVnlt1|dY; zeaHi40+1~eTvmo~)f#d$C~y)a79>0^vP2}$N1P>!C4L$#(NG6py` z5t)#{iAy7bYBnRJk@+4(xkRWFMq1a?v28GvBr8Gtc6-9!C z$eI|vb|OlnBS+57wcdQtGV|cq^Qq2f-^3IKn6WTi*m}P9UGeK{YiD;GuCCpZ<@$t) zVV6kqwL4WA5=>$FlG-oQV;K_oW@#I)UNJ!>hmj$6mayUK4+$!nj1029m1n%J|Cq8a zg&|=ZC`;L&Gt6c%^ti;t5G8qghUo?dkSgEa^fOWgUnXv0XxIbFHOUg2+k~oAbQu<$ zo3WpXpTR)qvDoIe8>^N$axZ_-7=q>;lfFaBWna}#817BWN2t_ zV_<+3dZ5e?X0d8dKfTa?WAXEI7IxKNzwWDCy-ElEDRn=HA&YxwkT$vVq#DNm6hO9$v&z+XZhdx zXJ0N`xMID%^4;xy)i?e#GBGU9e|l;vzwMU`ACklJ53cZ4k_@|)wbi>-UV*{jQPLTy zXU>Nw*jM$r{9R*hkX+xu;1GO!TQ2wChg;G_r4^*4>)7N2UVT&i3Nk6@=clL9+j1nG zvcn!quGn~h@%f}BlG+kASu6~{%r9J9bEQzR^iHPM{)>zt!)LMGon_i^A$WTyW6LDR zv^5C^2L%~4jx;1)JMhUd`I48&wpU(q*BF@?l(Pj4vk#nHP*~imzIGQ=dtC#=0S+&@ z&25u7XQ{1qIuw>{sa({+aA1YxA#>jL74tV-J->_Lz#Jn>%gB`>m+CGT?C90IyJXR~ z>KhwdF4{XVBzy>47qheO@2?iaY!SH}RbC~qzw(pL%!=mcWn^Ge3q2#%I&Gf9-MpT^ zj7$u*pu&HG%cL2mLB2YgWod8t1sGn;2PYa(BcUnx_BP!wlm9ri$H^tHwW--v|MlA| zkLGX1)|!8|%YCV9c-wo1k%{4N#J-xJ&(6;7&Oa-4Fub+v?$V9+4h#zfVb z_51J77VYuM4qNRkv6PvG;n$X;r>DNYzK%%5i+;>7ZSr3aw^sR^Iq&V)U#~7b6nx@_ zyaK}o{`a6XW@5B9QnS}3JIpI;Geg1>m7KFuE9d`$dHVRa+}lp~LCqtTprdO9Gm|{G zOM}C1{-iy#MSBl%`^W}+{GOY$|JND|P&hxjZ{j1dx#=O>#4y1n+xKQg-O7+tU@(Yj z{&D%D^BKmqL4oHFu6Vg?aajYyffqBh*B|&Uyx~)q7CVDP^~vOG0aCHA%%Um`PKjG^ zv2!r=EO6YC#%m$`#C-a?kDBqH1o#CQBpfGCo;=kn)q$UveHzazqcF26XC6$R*2BuM z`RIdtXQdqMBnpmovCZ6)qQfx5_0m75_GLjV3wHf6+|Uy`+3F!@1cO2MnwXtMcXyRK z|5%pzuru+6r1pt?6Hs#Ad`KWEOrXeF@#4Z2MO^0J*f|(VOj_F8N~^MKvPA9LE=>u& zT(Z8))`216%hC3>!~Kz!Z-qUNKQX-1Bf9neNjrW4h8d!6hG}0jza)z>7>Mh{@2k1I z%=d7mxpr&diHL(8Z`c?NN*)VuW?*nTHRH&diCNB<7O*n-`1pW}21wE4voG_;>Ec~e z+tZnIWA@rg&rrN?&eE_Gl+$LL=gYNUgOx=G=G13YeEs%Jmce1Yn!394{oRm860Csg zIc&~bU31^&sW~rmyctV_W%kokQ(s?Se|;9HeVs35R&(5-jDewcMTi!t1OnG!;L5OG zCg~c-zMOY|%NZOHZMR~Jw5|W$7QUBeV)#qA{L!B3wa`AyF#CnB=d4W(4YLZf&q^g- zn7EOl!3LD!CP^HgVLByD)i;vCVeZGAvr+{wKyq%$zb_OdTy#Ii#4tsjrNEO7DEh&{=0AO|!xe?k893{lZ}?JPPB3l>`nZf;s6H6N0UidEBG|F)r*Ftg5{F@po#_U)|PUH0}A8zaMu+n4?AV^@Z#si{4){rP0_O%n!&Ut))@ z&a4v%*gQKc(A{%=%K4$v_G)R ceE82;dZs7ir|%9k1_lNOPgg&ebxsLQ0K})q3jhEB