From daebb0b749f2888f47ff24040e1862921fac62c0 Mon Sep 17 00:00:00 2001 From: Erick Date: Wed, 6 Apr 2022 15:51:54 -0300 Subject: [PATCH 1/3] feat: adding camera zoom effect (#153) * feat: adding camera zoom effect * testing ci * testing ci * Apply suggestions from code review Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com> * testing ci * testing ci * ci * feat: pr suggestion * fix: lint * fix: lint * Apply suggestions from code review Co-authored-by: Alejandro Santiago Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com> Co-authored-by: Alejandro Santiago --- .github/workflows/pinball_components.yaml | 2 +- .../lib/src/components/camera_zoom.dart | 56 ++++++++++++ .../lib/src/components/components.dart | 1 + .../pinball_components/sandbox/lib/main.dart | 1 + .../sandbox/lib/stories/stories.dart | 1 + .../lib/stories/zoom/basic_zoom_game.dart | 37 ++++++++ .../sandbox/lib/stories/zoom/stories.dart | 15 ++++ .../test/src/components/camera_zoom_test.dart | 85 ++++++++++++++++++ .../golden/camera_zoom/finished.png | Bin 0 -> 34758 bytes .../golden/camera_zoom/in_between.png | Bin 0 -> 34158 bytes .../components/golden/camera_zoom/no_zoom.png | Bin 0 -> 31706 bytes 11 files changed, 197 insertions(+), 1 deletion(-) create mode 100644 packages/pinball_components/lib/src/components/camera_zoom.dart create mode 100644 packages/pinball_components/sandbox/lib/stories/zoom/basic_zoom_game.dart create mode 100644 packages/pinball_components/sandbox/lib/stories/zoom/stories.dart create mode 100644 packages/pinball_components/test/src/components/camera_zoom_test.dart create mode 100644 packages/pinball_components/test/src/components/golden/camera_zoom/finished.png create mode 100644 packages/pinball_components/test/src/components/golden/camera_zoom/in_between.png create mode 100644 packages/pinball_components/test/src/components/golden/camera_zoom/no_zoom.png diff --git a/.github/workflows/pinball_components.yaml b/.github/workflows/pinball_components.yaml index bf1907f8..e4154059 100644 --- a/.github/workflows/pinball_components.yaml +++ b/.github/workflows/pinball_components.yaml @@ -13,7 +13,7 @@ on: jobs: build: - uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 + uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@b075749771679a5baa4c90d36ad2e8580bbf273b with: working_directory: packages/pinball_components coverage_excludes: "lib/gen/*.dart" diff --git a/packages/pinball_components/lib/src/components/camera_zoom.dart b/packages/pinball_components/lib/src/components/camera_zoom.dart new file mode 100644 index 00000000..a3da382e --- /dev/null +++ b/packages/pinball_components/lib/src/components/camera_zoom.dart @@ -0,0 +1,56 @@ +import 'dart:async'; + +import 'package:flame/components.dart'; +import 'package:flame/effects.dart'; +import 'package:flutter/material.dart'; + +/// {@template camera_zoom} +/// Applies zoom to the camera of the game where this is added to +/// {@endtemplate} +class CameraZoom extends Effect with HasGameRef { + /// {@macro camera_zoom} + CameraZoom({ + required this.value, + }) : super( + EffectController( + duration: 0.4, + curve: Curves.easeOut, + ), + ); + + /// The total zoom value to be applied to the camera + final double value; + + late final Tween _tween; + + final Completer _completer = Completer(); + + @override + Future onLoad() async { + _tween = Tween( + begin: gameRef.camera.zoom, + end: value, + ); + } + + @override + void apply(double progress) { + gameRef.camera.zoom = _tween.transform(progress); + } + + /// Returns a [Future] that completes once the zoom is finished + Future get completed { + if (controller.completed) { + return Future.value(); + } + + return _completer.future; + } + + @override + void onRemove() { + _completer.complete(); + + super.onRemove(); + } +} diff --git a/packages/pinball_components/lib/src/components/components.dart b/packages/pinball_components/lib/src/components/components.dart index 14d657d5..8ac1a0f9 100644 --- a/packages/pinball_components/lib/src/components/components.dart +++ b/packages/pinball_components/lib/src/components/components.dart @@ -3,6 +3,7 @@ export 'baseboard.dart'; export 'board_dimensions.dart'; export 'board_side.dart'; export 'boundaries.dart'; +export 'camera_zoom.dart'; export 'chrome_dino.dart'; export 'dash_nest_bumper.dart'; export 'dino_walls.dart'; diff --git a/packages/pinball_components/sandbox/lib/main.dart b/packages/pinball_components/sandbox/lib/main.dart index 481ca781..3d65dbe2 100644 --- a/packages/pinball_components/sandbox/lib/main.dart +++ b/packages/pinball_components/sandbox/lib/main.dart @@ -23,5 +23,6 @@ void main() { addKickerStories(dashbook); addSlingshotStories(dashbook); addSparkyBumperStories(dashbook); + addZoomStories(dashbook); runApp(dashbook); } diff --git a/packages/pinball_components/sandbox/lib/stories/stories.dart b/packages/pinball_components/sandbox/lib/stories/stories.dart index c5d60a8d..d7409e87 100644 --- a/packages/pinball_components/sandbox/lib/stories/stories.dart +++ b/packages/pinball_components/sandbox/lib/stories/stories.dart @@ -8,3 +8,4 @@ export 'layer/stories.dart'; export 'slingshot/stories.dart'; export 'spaceship/stories.dart'; export 'sparky_bumper/stories.dart'; +export 'zoom/stories.dart'; diff --git a/packages/pinball_components/sandbox/lib/stories/zoom/basic_zoom_game.dart b/packages/pinball_components/sandbox/lib/stories/zoom/basic_zoom_game.dart new file mode 100644 index 00000000..276dd39c --- /dev/null +++ b/packages/pinball_components/sandbox/lib/stories/zoom/basic_zoom_game.dart @@ -0,0 +1,37 @@ +import 'package:flame/components.dart'; +import 'package:flame/input.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:sandbox/common/common.dart'; + +class BasicCameraZoomGame extends BasicGame with TapDetector { + static const info = ''' + Simple game to demonstrate how the CameraZoom can be used. + Tap to zoom in/out + '''; + + bool zoomApplied = false; + + @override + Future onLoad() async { + final sprite = await loadSprite(Assets.images.flutterSignPost.keyName); + + await add( + SpriteComponent( + sprite: sprite, + size: Vector2(4, 8), + anchor: Anchor.center, + ), + ); + + camera.followVector2(Vector2.zero()); + } + + @override + void onTap() { + if (firstChild() == null) { + final zoom = CameraZoom(value: zoomApplied ? 30 : 10); + add(zoom); + zoomApplied = !zoomApplied; + } + } +} diff --git a/packages/pinball_components/sandbox/lib/stories/zoom/stories.dart b/packages/pinball_components/sandbox/lib/stories/zoom/stories.dart new file mode 100644 index 00000000..653d5491 --- /dev/null +++ b/packages/pinball_components/sandbox/lib/stories/zoom/stories.dart @@ -0,0 +1,15 @@ +import 'package:dashbook/dashbook.dart'; +import 'package:flame/game.dart'; +import 'package:sandbox/common/common.dart'; +import 'package:sandbox/stories/zoom/basic_zoom_game.dart'; + +void addZoomStories(Dashbook dashbook) { + dashbook.storiesOf('CameraZoom').add( + 'Basic', + (context) => GameWidget( + game: BasicCameraZoomGame(), + ), + codeLink: buildSourceLink('zoom/basic_zoom_game.dart'), + info: BasicCameraZoomGame.info, + ); +} diff --git a/packages/pinball_components/test/src/components/camera_zoom_test.dart b/packages/pinball_components/test/src/components/camera_zoom_test.dart new file mode 100644 index 00000000..00f43847 --- /dev/null +++ b/packages/pinball_components/test/src/components/camera_zoom_test.dart @@ -0,0 +1,85 @@ +// ignore_for_file: cascade_invocations + +import 'package:flame/components.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball_components/pinball_components.dart'; + +import '../../helpers/helpers.dart'; + +void main() { + group('CameraZoom', () { + final tester = FlameTester(TestGame.new); + + tester.testGameWidget( + 'renders correctly', + setUp: (game, tester) async { + game.camera.followVector2(Vector2.zero()); + game.camera.zoom = 10; + final sprite = await game.loadSprite( + Assets.images.flutterSignPost.keyName, + ); + + await game.add( + SpriteComponent( + sprite: sprite, + size: Vector2(4, 8), + anchor: Anchor.center, + ), + ); + + await game.add(CameraZoom(value: 40)); + }, + verify: (game, tester) async { + await expectLater( + find.byGame(), + matchesGoldenFile('golden/camera_zoom/no_zoom.png'), + ); + + game.update(0.2); + await tester.pump(); + await expectLater( + find.byGame(), + matchesGoldenFile('golden/camera_zoom/in_between.png'), + ); + + game.update(0.4); + await tester.pump(); + await expectLater( + find.byGame(), + matchesGoldenFile('golden/camera_zoom/finished.png'), + ); + game.update(0.1); + await tester.pump(); + + expect(game.firstChild(), isNull); + }, + ); + + tester.test( + 'completes when checked after it is finished', + (game) async { + await game.add(CameraZoom(value: 40)); + game.update(10); + final cameraZoom = game.firstChild(); + final future = cameraZoom!.completed; + + expect(future, completes); + }, + ); + + tester.test( + 'completes when checked before it is finished', + (game) async { + final zoom = CameraZoom(value: 40); + final future = zoom.completed; + + await game.add(zoom); + game.update(10); + game.update(0); + + expect(future, completes); + }, + ); + }); +} diff --git a/packages/pinball_components/test/src/components/golden/camera_zoom/finished.png b/packages/pinball_components/test/src/components/golden/camera_zoom/finished.png new file mode 100644 index 0000000000000000000000000000000000000000..be784ada97497fb7eea0808c5b4af683fd8cfae2 GIT binary patch literal 34758 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 literal 0 HcmV?d00001 diff --git a/packages/pinball_components/test/src/components/golden/camera_zoom/in_between.png b/packages/pinball_components/test/src/components/golden/camera_zoom/in_between.png new file mode 100644 index 0000000000000000000000000000000000000000..3809f0d045dfc8fbfa34a5d954e0ae90b05b3546 GIT binary patch literal 34158 zcmeHveOS_G`?tHdd$n4+t=5bAGHT0OE-N+j4K`i1Ud&cGD^uFctVppC@dcsXO})z$ zot5PqcPn*S>dMRnk(x^=6G~Gu6)XufR0LE+5T5J$YQ=uXcHG$SpXYfF{bM5!`Ep&? zd7hv1bAHbA;`o-$>t;-|oMvWbHe>xaUvD)tdvA}K*_5eM-vz(9*);MF_}^QEt?RtZ zN_bY?;2-Z0yw-1@3Vz5__x{_=>=U!~Uw^edI;ZE!S2N5|AKbe2PeY#E>4636+_zia z3UpM>0B;%l;SBY@d}(&nf8KAe{;}Zg*)Lyzx%bvTUcUbK`#WB~G)sSL`peg!tlaa~ z%hzAOYya})QSQ-yzI^?` zGOkG?I_?US6ne6<7 zW6sc?(4PB`JMXWyu{d)iV`~4Jy$L^kx!@n4au@9kN%&~lS6_K~FF3#c(>-sUNIUYa z*OzbozF_{;?`z)uZ0f(So&4j(A47-xA1_Qk*?TgSOeSxpmlWX@@1ZgHrtL(w&3-I} z-OiS=JO;mOO}JLNuQ{}|dgLINuDKt2X32+e89|vYMr&&@WiyK|FN5zq zx~_HqEb78&rTA`I-LhHoRSehN-M!ekG>|#_NTZW_>jLU*PYa`<0J;f{*OQ`7{ z%+1YhKAY`=-}l`V_y?m^rXbt@c-+e4)N5Jw`&gZqi(Xh`XRSG`lGMjd79o09 zR~cSZomL2r0B+k=rST9K9Ef$i>}*5xURJkKJP^a%SC&R1k>Uv7&bosuWkorB2Iccx zGUY_`^UD)&>wR=-8;Yt(#=fuVX(>aob;`P8XMHC|zffFXU!TU$b)TbDvkjwZr9mu9 zWqr1bw@@ey?{%f~y~DE#TvUb>PG{P>3&YvBi;BGRz$sM9G;Iu`ENBDlQXUmN|GOYC zcC-W6Uch}EUAJ-P8I<9X@91_eRhI6m^n@Ba+%W|+S6PS|?M}0$>J(|f zC)zLA*WGq&SaIQ7ozIgkZl}f^;*E z4+At5?8B^oZU37(xkZT!TT_ps4wcboX#6S9{ofVr&srqDX7626R79ejwS{Mqj&Qy|;5zapjfkwUM+E z?XDZVk`2Ep6lP{)vjEP0H@KcB*D5NA-k~nnr3(8mGndI^vII%Z zo8*L*=dKTJlEKsGR)n`tgW+Yaxibd<#Irle#h%Lo8MBm4m;Jq~4|lHRwzjsS2Lyj>Z;;!-Q>Q$e2r_Fqe1ndkL>6E~f_RJErfbfT?VJZOg+z;b zUv*a&CQ|Mi>bzZ$c0RO?=vnL-kCw?~C|kG4#o|s5ElF2GV&Ni01Lq22AU=bzN`ovw zxzU03=n3H^f{>s7o{(0$yRw1F>1_oe*aJK8@eukJPcz2yf(c~y-z-CR-axxY zc5OjI{O+gvC8)p)V2|rAJQY#$aThB~WP81?ed$;w*K-`Gr*TlW9?`$KU^LsV-22=2wtd88~7 z+#pf*9WKR}{A?zQ`g|$2QVI$3Q7wJK%=F$bi?jUZK84uhGe z{PiSz@ZcC^-n-VrLZ4zjBDTlHJH04%EWT7!blbAF^RWDEWJ)A0k|OZDNGvRJYq_u( z8!DESW;UO0P7kup5;;@4&<(EL!y#q3bvExW+rJG0x;yOTNejbKMqeI#ocAR4&=etyRYBNB%r^t!zE1r4qT z0=+{sC178cb&~U*dOKct7uq1YIG1zbFxOjDw`QozjUO_!zqdtEc>7uxy{sqiQc2v7 zsFX(wIx|N}^i)wx%X=CKVgUs7&{bMO6_e?{V-8qYe}`(KdD#dar!*pk4|}AkG*KsU zVaG(I0CtAHQdA^VfD&f6V6el+r*S|MS)b#%wL513oyii0boDf7zu^>0x6E3z0^p!} zlWYU0FfOWhMWoQXb6a=(7r$ICaf#J$Y46^-ENOqWoeCceBa7OVt225mZgO+Wpq#HE!3Q132pLri-FX1Qh4yHRN z4b5egM$)W+VMARaSu!-UIys)u;CUoZeogJXy>UVCnPNh^XX5+0P4llx)YWQM8fN4n z$TP+G_@(0WQaIx0;bzk6$E%tCj)Q;`l~PA#DEjKdzc@=X=(@dJ2+Fjz3iS_ueuF^( z4Sp+8M7J^5x>eiP31xwd7hOzrq-~%qU%g^4h>Nn`tPD#x=K0;T)?B@DquG5?G0HP} zAU11GW$Bt1J&iDS7%vzBBV=av+ul;c+8sml3XkYV->($NzXlZz8Ev7lH7IPo^Q zmdfXKnVBUsq3=7HRQwuO6qoEz{IZh>>)Ts32LN+JYilsNnl$R5Kh;@$ckf3>0tG0S z`hNP#23HU*Gj=g*9}EwSM0|FIc)QTKL6h9Z*)pT9d0=hv)%%0L z>=(%%Xe@GT7Vty&Z{DR|puXC((_V`|kGub1=ZtpFHGWy$HRtx~c;;Y>6!<`zp^K(p z3=>$s492mEN$E8-+gMMruz6pz5j(P#%kH1f{s5*{%y)n?5d$lRx<;; z7GJ{E!_*t?(PvHXYw`0IRM+kHCHAIMRzXKMaf?qxwR?-wx9@6P z5K1bYy%XQnlbDAUsOCKh?h^4I=&ZcehheUvN=cN$@>&5~KL~0W1_I1xCw@1o>aUq3 z|77?qejFa2Wd}ADTsAu-o9+t3@U?=9s_YqKrcF|}cj<}y&B_o2&7Nl8hgla+VVd~;TRsO)Ym48Y~M5(-H7xA}U0th^e`@j^#*e93L>b`pb- zc|rgROm0A+fYq5@&|Sag+gT9Zn*4IMaa<4N9+&}gNl7~I+@?iW3qEf?RU#PN$yh7U z4AuMMUc}DsW?8iRXEk~@4l;&%8R}|TcMEYn|6rGWVqb7NBT?M5;(91!U#u`YI~#?q zie4cWjTDOx_8xwI@AR0{?fA)*?fprwdYwr7*M44i&84)sIHBt5)en{F3)gv}4?p{w zTevy*U~i1%{yE`_Zp(Evn;Dv0KfaCfEo^MI$+&xQa3_7BTE^&O1p$z@95e(uAft$g0N~Q=lsnVc`U}z~>uRrBtbH}x= zuaY|)xoMaGNL7O@x#w^xu0VhkxA%eYWEiFyR*U&$i6g2|^d4CKQ9-9E%Qx0Ujm*)% zXYp&ldbq1IlsXjZP0?$l-g!V(C@n2j2ho9@Xlr{#Rt30=!7mu*uB_5W4+ei(d;&yN z91GNi&l?oogC%@MU%Brtl0X+pJMY=(oK#C{7i`JJ|6v1_#DI$M1l@eLVm`_+Dl&|A zWWjS^V|?x{#@eiL5*e4J%%US8BE8hzAX7-v)!6En!%|KNcu@?Rp5&2T?}dmn{IXDf zB2p*0tXk!Tj_TQgV8GprqoL)105q>)(Ly+dp{>q#*4xg6;KNU>t}c-YE!_okA3GK+ zQXUXz*FD+}@@I9m&|T6<*GF+KeeFFRSwDoW3paMUt6Wnax+9UfX>YUHIZ&%z3G;q9<2m^#{u+su*5AW zuD3i*(A?}5gvDC8ddi#}78Eb72P};zX9$U<@eO5GPYCJG4f0!=)dTryR81z;RlL-{ z#GYjb*54mtQaSCk6Ope#!Z5d5{Q*c0=J1rsowX@x4%lidL|&Oce}1uN-L>~M56*j| zFzPk};x#;9ppLF-bV3!X{rvpo!%onHHU}{xmxwLmySx#(LicE!ZvuH_pgWD9=h^yX zp)~<&*feYR*~;6;rg#f0@K|ofdfm*uvCER@x?SGRQ>|x>_Tnux$ynVFT-)JWQ+5$R zB8wG3eZXe(8+QO~zL;WeH5Jrt3CPY|TwH)8^||=i6pNt}(O?UK?>R^Lv?d7%J&T~( z<`tL7nr#4KDGp|=bqn3EBct)Xil$PEvsg}1Gg-LZHzQDDD5#kOdo|k}2*lxVsKQXq za3_EVxJ7=B>&zpuLxI5!&Z#z@PXcNHgv8;Nofu+8wQJi!{lk^V@Z-EF%DZ*}ga-NN z&|v#b65fmqX6-gh2{k1_Db^xJR2`49MUz*9ENG=18HvU5E7+sGDE446zPI=)aDC6L zf&=xS{36u@LCz-S8Gd1-r{OtPpI=@}A8nz}Xuf)KP2a-$ZWY4QrF^=D2FgTUoB8J# zKM4gqbG8yf96EMjKiAGT?|sevbB@KHygbkOFe`gg>H>s79{|DuB3lK-fi0QQ7MWBa z8@I+^iT-DRbrKN;8hQ!Mb7%*`l3{{GQb`^ktv$qri30Q(AXYQ)6cz2lnO_1GK*0eo zDCE~=Iwa{7G-_431n3r-C7qOvJnwI>^Ihtpd@H(nc%hge|=QhQb7Ro%eJ!Qq70)Ze-wn6<3 zE{iWkP}jMDo=<=-0fi-Y|L@aL?BQoX0pw<)8;go!RXiTg5bXmOQghoBNP4x9{pSF{ z^Q9`G44x8f_4wwxeGDTnAnr`aJiyIag6sm&v^v2K-ozoTm%*4|4=1yyYUYcE*W!`; zOKo(MsvBc+`pQMC?p-?+zY3I&(oKbpLWQsA;C3!PwlVu%^(IU<;FfnJp!EbQ9-jQk z!rW~FB3CrhT;Zkf3B|O!+ky=ropQ_+LiMt*)A4i=NSYOB_xO=W#4$i9-+j^b@B#{> zy6*@<%c6OOWlAgo=_bdux<$$}#^*;(3YuHJuP6 zLHTZo#oh0uVQhfFgTfy=^pozsBmbu=Kv0013j24^-eZj?FA$6J*--2&Dr&Ytq~LAz zk?l~!kIg*+MU3b)lj^dU?M|EB08&*AN$7#|X(B}`>AD2;6ks*8a2P7SNT&gq-Z^r; z^rG`c@To#sTwGkSr~XB${&O*&qRM9Bz^8%?ViF)3%U-WsuVXO?Kf2}|{XI>*JPr#| z^Y!wzAXnI8C>~;tEwzfocM^bZ3@k=Kk1G!Mjle3)(2J)soh8$;!(PxU#*8sRpYMq7 ztCV`_`;#zM%G%nsAiHS!zDQ-~gOAg_eLC15sD#*$8U) z71{N%0w`D@+-VB6e+mlAh`dzz13Z13&)zuO_1VI71Qoe`TzRa333Tv9P!ti$kK5(# z@4ZSj=!LGA&U+?G=B)YlR7eX`0-GpU%{_lFeXUFN!?D;~XBzJx@2T!_hOs`hkv*^x zWq9VG{{aDOf4`4mUIh~RfvkNI_uC~ZO@pi#s>jVB%~Rcx5|PyjoxFo>Qme+8HyZJE zojgwJE@~O+L7Zn@s#SMjFp?2rC!nBGjq%LKodm56Aa>nAX9M8HhleNXZd*{6i<9R# zEZYWq2xhe9|1(?Z9o*F)1>Yq$zg5L6ej&IY={uj}82Q8eACj)toJiVn^x8WKKJTRe zYu0=JTJY~bQs4O_`0X=Oz3$acn~VB!#~##=9~}B>sfYL$8%R7 z>x>!Pgpn8jAd^2L`iUCzDjOB@3a!YE$n!7kYd=*T0G9%OwtQpMx;nqF1sRX%-{UdT zbpb`ytLXBS%E<1m+$H&G*^AJdpMbvR%~VE-cX(^ma#EnnS(RpA*-m_YdwV;gE!c@+ zN%Z!|Z-Xb_ayOO|y^ON&G$R;aY!ul4*;3X6Rh6U9zgdz(X}-~W?DHh;9wO0MEE-M` zP1R@tSqC7$NMVQty*St|xZN2jNNGMzz;!c&k`{BcH&Sh*|lAycmOIazVQFWcD(koX6BwRHWb~QjOCLGKnVQ4g(H&4cU&QZBCOQypv zc6Yr(r{cpp<6VSx9?dYufC(Mel!f`Tly+#vap7463M*xdwALFlH2wX{5zc}aR<#6l zjtt7i+DS;wN*%lmTJ3`Ztc9lDMbuM#Sl!p2MoUeFJ%5x`5cAzAm|Kb9vPUknEfIjz zi~Mznzn5Nsv>N@GSbcZJ}6}Z1v64z(P*393o#g5te>BAcKz&o!Ggol(Q}oo zICU&e$RI8Sz0J}j-4k4Cf^M*s8oyjBIiDcO1`{Iuts79JK(bRhz41mE$iR^i)j;a2sj0aQrd2)8+GPyW%de^~7hy?3 zp%iP)um?Y&6OJ-@!&)159#yQsz`z+XVPVA;wP(pZotuhP$MN?w>{7*@l{W!KJtiaQ zFJMM4Jak4toT4ylQv7YOJhErPU3WZH0OsvDCUqF>rWNK?`_sj@(#dZ!4->i@vS~Wd}L~;=yJ9g~00!n~zF2E9!dw#2^ zqE5Y1#JqdaAga>s;tVaKFT~_wr5>8I@|C46Q9$x!X z#46om+UO20RWWDfk6vXjX91`mD)TpHdTm}ZC;?%!T&i?UrP0>O?)xlKVD@XW!deqy z#FWTl`-iJs3mdyTTs6e!($bQWp=K^-erp|os5DjlS&}taFkYEcdJZeNl3V5<=3&P! zGz?-5A1jxZR(+(*btif0@A+4Mqzu5bngUb+HQv|Mrp!l)m?Q`Bg$oye8Skl~agM5& z8RfS>!TX^VOdq*KpQfP!Rmxk~*49(M;@lsGWA*#W5)STfjry$s zxKzn}N?W?EZMLH=2CwgqWIV9aWL>^|86_aL2P~vJ(t|@nJ`jg%_Va7{f{6@79Ln1CjLM{RXHu4kMOv0fmRklzNWm9F z`+o^*O{nF&+vQ(>plCTXTUnK*A}Ch`!>9mq(^|n?_dG#vHn5GSb*r45+8yb(8U)wD z=2}LoInApY4s`%djgrgdNg&v)r0yqF_3n0b$5MAu-Bng;kwSi?y7u<%uecf_Rog1+ zt5+ZmCE$^sVi#fb!m-UWN%BMwe5q3Ia~E$8w%ft_!zAPeHVKGAnlhxs)2C0T(c}6m zg=ew)=U8N-0Kgq&2);k8%FaDV4%*gwg9SDRiUv^tWpnk~OwV7=UDO*SO4hD`fcd)# z_p4-c=Q~DircfolqwAaFnO9}^1UW8-NVgdP>-R6WwX*uDs-nULp1=zsEAAL@kliB8*Fi{QIYMykViHgh)BKK37Q)` z>dyqF%+1Zs(u(@C%wzTHsjlo)IEp7|7X087X1KZ~ND2~yM;-{pm|&$VXGr-ePONlR zdfYI4CSvMxba~QFRcBIC(gobf_$f+}F9oAgsLjx#naG)a%PMan zzwNvzlr?r>H0FNh1IVvzCRL)?yt=<}y34_LW%PX%Ppl)^ zHP6HNqC;EnxGD=#8cU6)tmRN-eO1XCsa0zdcou|7eA~?rA!9-6KTvdgKn+M}sRAKf z)kh}Z9lgt+*IK7zC_7apPd`;haMzbOmM+YsZv)c+iWpT&uV0>u!Rf>M4a$Av@DUI2;3d7oQ-$wFpdj5e)wMHIU>RjQOfS}(bu62DH;l*V@*%H0pD(fSx?2|-M} z&m20huv-zudrTUoTtRxV7`=j)qyzCHDnvAhj*I^bb}-I1wV(|ODzQE(WtkYZH0?l? zMwN%PYVGr99T5>#JARySP5^7Qa{Uo$)$ zya-GQVL&a|-Byo2Noms}Q=I%Wzyv+LJNt%|-Ey2kMMch##fd4lbLx&j8e zdJ&co7WRc0I)d*JL+w!9p;DzRr=sr{+{qL=2^8>USG9+KX6+hKr`uxgYJv%=?X--! zt^74qtcI-O*}B^qN?d##chrER6%C@bj|$On9<;B+3c947@ksRFrjI`r*D>zG|lV$`5 z&kl6Npyg!{gvLswRoOH0F!Dl6!hg-Z8@w4CEH;#Sk5zv4UCuW zQ-VSJL-+f1hk;u1aE-0G=2BtaG-T>;olVTAup&+aC085{hT9&HNOEM1@JKRx-=;vp zF@hv66I)Z|%z2UH!r9_u_t2?^7F#<6>xr<|ZT0E4bQjTMPo9*kKGH+6k2UzNTz5C; z(ykt6jz|@Qlh9M*X|>f!pNW245sc$yv(>EbXJ^;%>T#2fQYeZ!)D_~({-udJL;;hE z9H9Y76HWe4mjhmUunB02p!tDF>GGbQpP~J#hs1kK zq1B%goWj<4Xk3GFC)@cko3)VY~Z4ek#aTn7l0~b)B zt7*{wPJn8P+{dJ#GiE+URi7NNS3^w}(C^_r3&e)-0s+gF9{b`_8a;lfB@Huh-XcNg zDN=btVQ~p^(*V%dSe%4dc(}wjapx3iW#+in#V+520a6d(|hhttg6)#c?a4dSErH13sDA0b&lD?p@G#kCP5~c zXJv#SV}ucy{8R6$?%mtK1>Hsz28vu5{ctDCIgc{Z4QEeRo@L9m)ehpqyx4=k)sjXI zbqzlr&gfzcckMrvLe}j)GGZ{4hq>R}SG_is>~$=*PBSI_2wT*jZXjb& zBQX7MV?KbRw6m1W&COkwQ>IKo8B`d9YR?SV+VrA;2~a_{N~ckwU{`B5Xn<&lxnBnf z&Xy$8N^6_%bJ&fc-W*P!XniwRI=2`_-9-9VVN@_dLZpi+&k4&Iq+o5t&st0rpEEQB z3St*27{0>%q-)_*R*7+fl9XmB8ZM_9HBn|6*Ft{+zj1|;Z1;k(4CLV1a4rl%G1vS@ z*#yuK(>+TDTqK*V$v)Nzkixq<;;9!tWn<>D1f4j;HxcP@=s=%)wU|D9oIVvmOJT)Y zN?5Xlj3=m4Nq)LRKhM@;pFPe})!t_&d2c-(80uWT51@&{>3&> z-%9GUn(Ly{SmG&TC*2k8DcquSq+FCQvqKI`7QC( zRy=Qo#x+pgVOvukHvdS_HcGCYYv>_|+LAb-MiUw{Lt`~|8%*0K^#ZIZGY%3_(yw!Y zbvhzViWKKMcT1k6fgA36SpseaA1bn(PW?ar0(S4LsZpv>|lv z*A(949Kg6GT>jnX(wx1#HK{-ea#z6Ve%(^Gaw)9n!Vop(sMh#0+TT_;M%dWh2LMmYZEbDU&28=NGvKwI>0?uRKrX2~FhXlu1?E45 z@Rrisw!>d?ktQS38U_Q~-Ng#%nq+usi8I6oq|z=!Z*Wv;)3I?Sz0XFA>;~OWf`SUv zJ@CpEG8IF4nlJ})%K5t?7Ib_yaV!{1xMst=!{FLOn*Vp-#loQ)NZ$H~!9tna+wT%V z{SESXtW-=#QUeIq1l_%*r9b6twY=}wB;q4|=|It7AOeFZYL~a|*xVG5#bUtJfH$S* zD#r_kAkt_7c!T*5?BK-WD18L_^s)KQ@YWk7s2kj>F&-rRuNGO&1SOTB8hQwrEX#G2 zmsQ}hhPkk|PiV^h0@?;7endzFSMbuthKhXwr9>5;ip7c5kX$iVNX8QhA zRaQ1rS?9oR3sAwU}kF|{2{3o2k51v7w%q-L&%*KMAG?)$;Fc>m{-QgG&P%;}&L3PSZeR?9W9TDa1 zL$10GCI>+^!6Lw#_|~bd$Ve10)Px@SCtMxxyxeL5P@rgl9n~fIW8n!aOwc~@ozOwN zdR#f2CwcMWAegRI^Ul@pJ4Wb}%N?BHyoHJC$V>cK&1@D3yh}hE6_{Om@sw8{Q1#vc zCh`LFZG~g^L;IGRRGu1l!o~sjXc-R9auvAV?ksR34|jBY0<`EkUm#kH&0Le7I<94O z^th4wc5{6k^*1l!n6T%7sGK*#reB6Yb5v_Qb?~<_`q$Cm1}~@}PX-VNTiWo3BWO4M z{sNw7hu)-q-?%Do#w(dVXBh+{&q5jFb|-k$&ix+nIrbT*c;sKZ4^SRHR_!7Mfgs8wd%62Bn*AtEint2%CssFFt*IO;%G4OKYF^(_L z$KeI(IS%i`=bSM~mi}wk{bor*Rb^%6gJFXdoK%uRM5=+_{6sBwgJ+%?re}~|lGFbPKg z5}bh|4Z!(0EfeW=y0}U8dI~qJb@rRN&ml0V)duq1Rsj)G8E~FpPIo4lB^Xa5HjCeU zrJYc;^IcSEpo)qED!lp_eUA_k;MTE;VS_cC%&a_+XzKttq?;7TUB4sUl8JIW_hq|p zE|2!ecXC??b}Itc9JeDa+*>RE7d`_g7=0Jj800`Z8Ylx3b+kMLnJrAqzppao<0nt1 zx#l~qrU(_V3LwgwyQd>b@-vfi#h)tkaW=l%{RSx7HZMs4{tVRZ;BZm(=rTX3RhUf- zW%~Oe_XNxy8FAsAqg8@iqu&$ZgMK5Mlz@ym=PNeINtiz8U0UI(wbCw7e}l1|ZB58% zK%ICZbk(B@1Om7TIU`lh2=CVcOof#^K>Tr%@!TY=`m6Jcj?O>j1scG+_v<_wTb#sk z>b@_=P|M>}lc4n_p-S=xXa#ckHoj{%&r2v0VFr<@l;7-4I=q+JyJjhnKBB$!?37(Ug|m7~>a;RT*MV?Lc^d~4`$^_jny70n zf8PEe@~wZeehk_muXA*0*hceQc5BOny&=_y7k?I@d+7V&8Q!_}S-aa;jQm@YTUfEp znXhWAf=w&4Sj9hHe+TS0ufLz>gOBzgr4am^pEk&mfSxO8YVvcItF^wPdPYe$9$#2- z5KZUPF$f=c_vt@he{WNo8A6ufyFdB#^*72)eed-b)_CP30}5s*WndbpY4<&;J_p`0EGB zbBsRK=zYeE+#U;OkIin~9MZ-+%cGc*Twh<`rm7O0YBT(O*PZFFzi$Zj)&!2MzTO8u z?_iLiJge})h#(h~PllVwdFnS-{mp^D^ZLWiH)!Dk%xZ~oaRK$r_7eCMjtDCts(R&Y zL-_yp@FP}lS2_96M@B(r5rHp$VCxZU>inaP?)3jx6T+uF578P_)-;X;R@`RGr#TpT zkkN+z?({xqS-|)0*{xQqeb{XKI!UV{`p^@E1}~hDm)6vHJ7a@x7%-y7#%(HuZp=+E z!3T_Yd0L2MiRGyWvWheyr}=2lgtU<3Vq*`!$%>vv>>nA8$#W3}1qEeA!8X9HZzf{< z6+YXXZOxMk;_I#*s%BMJuj++q;%K@lHZpSfSAlZ2;8K=Fl6PY@vL-Ax`wiEG@$hi! z&6dWffNWHJzz*8)MRZ*bE{;`yQP*%CLGZQD-Vk>?5!~I-N~h(~K`XDB z81@FP&7rVMC0n29$&ns56McpeD7->B1+d-0>Z(Ip14}BE)>jY%-^4c$L#)idKC0jGAJy#NdFCMaZkTNPPQjom3Hs)(|4bx`53jbObXRvO zhBqQBy0zbgHyfknD|fOoPXcG8Yct5dsM}f(5=y-bvKp=l4}5jfhpAM=V$|x zK9^fg#G^etJOJW9m+Gl_a4HlECA=Cz>a~(L^TvS{F!xvBMOjfi)$V!+2L~S{@0nz_ z*CE1W;vmpo);xcne*nqoCwXs%M_s{L1@#qxv=!vmUE8kejy(7T$p{xzrV}QkXQ({< zRjsW7jR+vj&3&_M^Z;OFHXcYYKJFrs=&}Z4zBs{M?cH+RI@B0HK((G7oF&o=1eoT&yqD+}B&=O5JfR)~r; zG|;V15FmdQZNk3KcRzdfLvK7%;RQ1wnuD45gA{mvkQ8bWDbl3GVocPtmMvS>EL;Z@ z4f3O#6Jr8%GhzkCpl3{hjJf*cpHH5Q*Q66nI>Dq9{H>lb86K12F&Q3{;bAuE1b^)W z!(dtrQEiuVMX~10>wr`V$vbZ%qF8@G8!gR#yAp8 zM#K0){9l_JC|m0}W@hhnt^fL~?Jte?f9+x?7YI&Z(mLaAFqvt`4r0pk@FK2a?6G+b4XWx5Y``XvO z(_T4fzGv>NrL$C2ROaIM?mVQTGSgW_MRmrE>F}E;oRO*UW0KFIJv&tB)yuo!#Z;di z_`@^c2$|t>Sw&@)3V!GI!-2^?4cq6ctp2L3Y}$CLaLZrwS0`8=oaCr8G#5T1KPp7M z6Q?Q>HcN8;^J2}EZzs-waw(fUaejHuk%?248FoUhvKJZa*5=X9-!(}+JKrcIoG zrM&orE0k_gM$1GjD3eHuEs9uBLawq{C_+J5LzUD*5eiDir6i(?P*756WoMxX1!cpf z>_Ziyplr{Ts)ZsHl)}RQt-`{m`r@QTh20_1@6)U}{#O_89$3Bp{Rgk-5f83@aQHiR z!}j?1FUQN+5%fRUPj2>Zx;10gH#--3BwyVyKFP6|>st}&J|oe5)BF^3KR@H9UF4Tm znk)FtjQ8yGj757hy5Hyz3$)4`h4E`9eI`>kRQ|)UZ|(i8(;dWpR{Pd{^Vy#ZXMRqz zsHvaPch}_4MOk@yNJOP`zzNF80VgQa3Y-w31O+81fCu1Ef&w@}2@2o@B`BbLP%;H@ zf)W(K3ICsf!gSZVYbuJp07|Z}qN40nAkvg=95_MQdia0*PH5z_ng{KSzCP$l& zZ5#hoVM@W-+uPd=2BMFy!W4+s?%FM8l=fH~r^>6^r8l3pW^C_^oUdM8pXU~XW3a|X z)b!QSyB}q(Li?exW8G%#rV%uA!+~xwYg~40skOLaML^RH7tcd=AtvMDrt;6o1q_FL zYa1J#mC-)sbM$p`rWZp&oDEme(u-K$*Ewi(=tqe4BDoHiZSQ1?!S4rqgt-t!ueusq# znCjBM%Q2x|eIKFIobVYqbgI&qW>$~;IqEn9_ zKi(IviXOFJsDZ`Alj`iO9@&DwW^0%H2CFM9?0wuw!PRzcsdT!@A&na?o|9Wtl*r+5 zaFst4G*P5|#o7>HpWfx=)oT?O7vn{DXg>VvwfNHUF=@z9ON+~KS%%-GieFdQXX%W# zMXG;$!#a1|pj`AS!y>KPOpo_yTg5M1UV_WNJ-Qg#;cyaL#|Xh&_~Y+3#86%QAUN0U z^y$-IJFO)TSm~8@lM-FeWXtrEYD7+MLc3+Vv$L~10t!Yq;=P4Tac6&9`BnY_jhLRk zzKv$JBV$tGqS{AWw{G?R_~CC{Wjrw~L0Br?SkMwirnJAwv`R3~xVp-Hb3*&O=U10p z-_82SKwAJ+#(tP?GlEX!2Y3AfOq`=z1vCe_H3z=GJ}RjRcPing1agSm-#Sit^2w)m z?3c~U=jo*NGEHkjx|*Ll=lF)#Zn?@WZ`)Mf=5;cq%~;$N6R94yt+M$r&HoNZB=WCL z){g<}apVoEHEE`4DJdy9vitm`^vWsojef&HFxoa5LU1OrH`=<*K$fu5HLir;Kmq>5 z90DV9W$-$@cl)0=j6`pbUtM+5Bu#sAx+ssAmRH|H;pdAVZzlve9@We3ZZTavCoYxM z!f!|$k{G=o*j8!rqxCX}>$~q>t9TF2dY)%(ZT)RcF@#Gs7=qIe8gwa@bS5n~^w+`< z4s(h8Kdvm$4;}HPFrT;w1O#-EQ8Kp1=brltnS~Ao1VspbGCbX#;Fen1=*ddKNy^p7 ze(Ulr+LAy$nRtKZL9<$yO^dah3Px&k8fyGab(YUtlqPU;H#J;7FFuu*&`PXXYdSgf;#X zM(qk;V|mvaPZre!+!g@1tlcnOoOCF*of3Nv|Lua?m&)7Laz|p)DNMDqmCZY|#$0G3 zo!)#=P;J*o%J&3m_U2Q<)8X-=U~zF#ZSIIzRMV0+b?f3e-|~;x+gF{PqUtT?1!&?| zhJO6}ewQ8OC!imc06c}wW+MwLn|-tH?um8<;J^>|7PPqf`1qVmIcj))4p>+9M>-EnxDn~QiWi^kdpWp79SAXaNXKobye9h!Z z#fCqRkIAg(j`{xlx;-P1%JR>VUK@M)yszk)|LJ3TbKLXKzUc@XZ}YmDOlUdQ@ck^f5m=FYnXWivfLw;+*gQ;r#B_BUH4n1G;n#t;qgf;IV_gxSt{#Xj_G}> zv_~}5TU4HYa`1fwZB8N+Z{JHQ6P5P4WcBG6xvLh}GDf&XqNE6p>2dPozTomPw_>Z} z5x1W3QYo|7pd6I?O2%H2pH~E%cYo{CkWXz;V@zfIw9UUvkDt1a z$CZ^)Idp+wI(|qJ^7U>uf9OC)?epO_uR#3UEh)0aM`TBA{EnxG&8eG99dTDFK4UZf zis$z6eb(5%r+s`_<>T2ReV$IhxQS?XanZ=r`i`-Z{QTIAv@nCTP*Hu2$$Vr3Ene*c z@&tD5(!hoc4sP>S82pZt@WW*~^G7SD?026ct-ZUk;FuT79q(4eiQX_`*ZW6ST}S%i ze1^6jhZ@Qv|Jr9e#u00o{Yj#3(*IPoaH!gFk#O$x^Y;U(j;e3F85??D z*gHaSLTZ2is++Ot6f0vcy}_-(eNJP|{K)eAiv|2T@vzOTzJgi(-UBwKx!tA=N7`HC zBz0hr5N@9p>9=G@ui5)!F90`BN32SL#i6s0qP5jtkJgdFd!qm~=JM8B4{o})E zgE@9YjK{`q2*pmrRsaO6hkmr|E99r*D%}~(xcvP5=Wj9qCLbB(7`9Oz3%MyPW3?<#Y!6+aI%cBdlcw%pz&l;s`}p@> z(RRx;dkk^TO3HE_|B0Hz)D(+&db7?EUHTy2M{}Z!Eq(Km3<-7ovVy zwWD7$=|n44lYDuc_z<5%LcxdHK z4FW;cx`OHC=}auIrCoAgm=t@p*PrY>`E8imrPxl1j5Iw`SJP8BBhasy7t;QndZoqq zM+;3nlsg}e8>(Jj{f}Zwj=xSq4r!HG#+?}JAdam~le}{C2Bz44LYrLw)BTn(wJ^ei z%q6c#m+}%vWMgH+<=eAe`>8w+qGb&?Yuc&lrI*~NBt_kM;69r+#CE5K&f(79%3WES zVdoym>*P0jwwRR@q67i~qIKjJX>-YNwkLamh0%?xi?p2vR$!LoWPWD1i!E=ocl*?# z+V;dlYvabw^Xj9{V=d#-aQsG#Q`B_AT<&b^m|N@FMbA763=i_T$=?vnQc30wRC?yv zR<1|B-LP+VTi4-o_l3J|_!l!h*B6EhhTf|$8uF!1HzYPfw^yG~2t5d2P zn?;XtmBi8K#5r$PVb+9p9SEL+ymGAqcv|ydT!BkakUoAeH=e1bqoXsWE#qkU(RA0q zOs-(=g$MBX#k{^UJ9f_DjM^nLED3z77s1DZ{eJxi!nDLoKObYe&EYPvzWp#`NS~HZ z8@`lQyQQSLv(vSCd`t{PbhL)?b^*E@SN$J&@!g^Ah-~yv)-eStP}mV??=8qK=xncy zfr^`9&S7{$pdn^3d-iNRrK_v!QDg7fePv-RqPpIO8CUP@Wwh+CZ!x4(XC?8IbkC)j zyE)$>ST5}HH5g`cq#U0_!ezD}^@2CosgB@YBiiHR>qM?-#Hu1~=g4%gZHKm1R{hK2(=u9=scO9_{8!_6q8pQ6VDfi8)dh+9OQ zxA@${lwCd`@2pO`n_uhpHAqNG+D}1Dz9L%PB3EyP02R5EfaU}j&)~Wn-|fkL^yqgN zMzt4-L~`CtxL@0Kk3-@J^TC~!4&{Wi?8?LCga{6~oIB5=mQ+fwCU7Fz-`lV&o!I-H zli1x)xPlW(T9NS*KI*^``5#vB#mzuIx$k?VtX!TQU5w@x$tB`qu(Yf zSJ3hL{(d|qGc)tmt5U~@ee&Y(Wd=#Gx0q*6@h#>hn}(aXRVU+fZ7jE$ni4Hi?Ck7b@&Z~? z+Pyi%ysbGr53cTo6SnDERl#fOx@t3qMz}1K%I167jUL56VBK#MS;#~^f_d_`8!n7y zuQO+U%*)Hei!@va;W!R@SzGyZQI-Ii(f7TGLO(-kh@87>4dgpzV zkd6(+%ATH6#vM?J++1BXw1X=5Q*hd016Kvo5m{J6ufOA@BW7%Bx<$TS_D88KWc0nd z1!c5aIBFi+o0F?mD;Vz9h1T19Y*aET93S(fw0n+=JXPyb5)-Kz-YXy{Q3Ic8l|gZ! z>y*euje?xdsCrLO=P-NlL3i|g9fLDgjl7@fz(+$&`)$kd@UDO%5?9pi$XTk-tX+}eBI@{Sx+zC z#nYe;ZcniIV|~<`lu-q9ef&61b!FhHXp@l-M@(zO z6rA34;?WLRSa8)Y)YjIbln8A>zzNX-S?9tymJ{q4cfu~)IyAY5u+aKxMvIi$Qnkl) z`M+c)Bah}6snc`Zp(#!C!D>5CfA)V;Kktzfk+)%xC>UYw(Cu8ZZNaCtk8)O zAI3@x{}dU%mr^1O71A<}V#1TpE=5kiKx$WvrbXTAEc1*Q=%6}xX$M%69r0 zq?C*e13rb6>xKLbrF6q|;gk2jA`Ic2gQcjk<(#pN^HZU51^2b8KqS$?uLNR_EqM^S zRG`TWtX#IHX35!vjcL#hpO?HEwuIR(At9$-g)SQqe^_f{?Obq;V|t z-GVgG!&;}PKT!B==)P{(=dVI8KdgA(uLD{_11Rm+JwuKo-0dOE*$7|xq549 zPy%Bwa0cGIIZSbJb91|OtRMQlpw_dh6dcHc$EBFiS>iQQZlym`ETQhniAr|_2LSxn z6^3yD&m#?Y)^-0zPy0P#oszG)%SO=$>afX@jlilo4jzSA>GbY-0S01Rjq!(rgj(ZULTx#MG|g;2L2h zT7`(!>t~>_!;amDaX-eafYS&*PcQ7f9w-?7XXk_L^WCr%5??>~<1dsAnnVI(4kJ(2 zMFDD;&o@n0AAPERfMPWG=XW)Y&FEsSBssA4mlO}YSTk>=9ugab03RnCX%*6EPu&(&9?}?wDEqXEhBQmRX);~>jnT*&_*(95V zyQ1#wr38Dw$#)43HW0UjhnJ2CSSBbnUV9YeJ$Z7|#gj_N3(i>=^~mo0xJQmbpIC|r zIv}A?6K^uwHeYCOk9qR_8Z4;2%9uSTw5ygi_g}(QqH+X<0&6B>D%8VE=z>N-Wk@*t zy|h^unWB6Kn?{s%A22gpTR+?KhaY}8;qNbsA_Fjk5Go+k{HVs@c#E*Xm>_X+O9~G8 zTwH-3C?ezDzms#h^DfJFT}F^uSgUH-s>Rw7#cJ>K3H_ggsmrOVNUhxktgFoOY1*OB;Kn!VOT|>j9 z!otE}S=&jQUUNg46RUa8P-322O{ZJ9$W4&xgpAsBaxx~1s~)jj_&o-uHAd{e4j1zf z*2l%gLG=PDvvk;=l|N6nx33Q%Lvz`(Wgr))g=_|CHju(xU*65aN!~4D-JHE>;~Q>Y zPtUy;AyN09AGE`KM`5rHrTxR-cTeWq`E|5H_8?I8w2hmxot7IO#O^KvX$^-2$%8gp zsPdHM-`YFaGxj~!Jp<4IMRM>#|pfznF|fQ<(K7tZ`8FU=G*eD$_95L4sWXr8JPN|h^I4q>8PH00@`Pc4XL{j7dJH=<7P>KiC49t$pPoB4 zH!eONsA;r1BhA*>GdQ?to-MQioQ7;`S{QGR@7l_`I%|{ROpA_(6)VZVmVS?dCeY)b zP3;Y=YDWlO1%y-t;P7J~qQ}r?QPcLQw5EX4O8;ZvZ-ZM z*G=_FR8B}8Fdc{prZtTNNyb23z3=b{^LJTpGdFy+BD_5&ZPaC>V-W~TqkPje7;o^g z&`m_$Y1GelBycY6vV@3rBax_K9+nHiH{JDY7QXv8?#0=QD7dswe`x0B<{n}= z8fO%_0VHGn5J)PB=3*JpzMnX92+LCteupLN@9B;@VCKaBo6mTqkn-(<-Y;vpNtAJj zW)xWPIR^^&(UT_=R@hi&b(6{%=IKjQFB7u47}v3BX&kjTOQ!02&$NfZ8us4>pl7e2 zv4LG_SLrmjty*MGX$K|`v!Xwt@us*01n9w_X?Wnpix*{w>D>uzK7n5VWU%Y?eZ(yB zln_Z3vHEzlah!{%T^;b^JYY#XQ&uB`Ez(pll=V9W9B;l+|4)>5GtXcvgOqBu)ztL1 zsAt`Q+kPWle9-Mhc=;%bc~k(b8B_y5j`RH`uHE3?3prH&+{n~K!QuQ!!UGaug`pw& zD2yPop`CkE0SgAGZ$hujH2JxhM^CfQE`~(Kf$m!w&l>H3He)M(@Wb8cP9UOwf$pbI zXVXoxo}EOdm2CPSAt#^ZaSM-E{JIuD*6BH>0bIU?KvN+zos3sd6a5J(o&}9lYBQ-_ zmK@(q|6=1D=nt%!b+2C^a&>h(B+|A>>h>?5Vs-aYnth5uECmWUg-_F^vU>fCP4q^C z0MD5fz||nU&Q=njs~aA&&#qJRyTsmC-f0I_9*o9QMy0HAG7>wu`Ij*(b9^PpHyD4YyLBH2MiRKq9a~gx{q-|j|u#@5f zy^gra#{!~uIz2m;lhseF6M(egLvyp?N7CjbiMP6|0dWSh1cbO-w)JO1xuB<~@6GCm z>yru79T+@KvUsRFt*X{+Xu zwM!mI-K{5E*WRNF#Kq}Q^t%Z@n+f&WI>yG17TFC=O|M?O*p0BmAvG|csP@ilmcrD(JX;vKGZ!N42q1@{0Wu5B6X0^`%p_PY8owJyVJzQOon;EdToX&PV8@y_9LzjRPOMG4B#<=pihfda_IZs62(XzvXzg%7C8|b8 zN6SotnNIL`HRRh38Vq3$@08q4EQIp;MQ>gdFPB6wCdhrt;&I$=BSLVn`y%=J6#MwhdV9kQJs zjvOeDy&Bur{j@VLvBgZMq%MRp>(W{>Are2x1$_-q;WGRDY#kk$$1t^=KL1N3(;qRH z+O#oWXh)n_dPe!ATi%Xdb+)C#_nDlzqd^lULDRpYpjQxNa{BB zQc`W{B`(kNTpcI1@llJ;MyHaSHl>5HcuGx3r;e096U-`mij(iEn0$yZMFR;bNWQiA zWa{F*2iq%~pf|*1<_^>VdlYqn`U^DyK`A+MgCy}RZM`#RjvJxvL3cy9tW&ZtkCZdZ zQ+J95Va3thMcbB@7D$4(z0zG?P2FL`;g8sfX1h54aF6o{iYO{!c*^()V?5Zd264t# z=YyEi4X4Om@)7?7LTz%?oj;-dbhLmuzg{Oyz6(LPrQi_iqnS-psF;TrrMu}0c|uRC zyk>^}eX4#Jb+d!t+pHy8QENJE!kgD0nscCm+LuY^F$QxA&3)9%r40vZ>*kB?aJFSt;^jVv#rm3`ULwhf`sIjBpg(e<``%` zHn4UgL+G>RNU*pbgmVgx5zL_tCUQ$ODxG3m1Ww{2Bj9!x?$@DzMUoPJ@RDB1Y8Xo0 zO6^CJ!6v>*Jg)HluZC#9z;ct2ZFr> zO&I~yR?CI3jmZoXp+kja;uwq$wpBW9BKiA2p69#*hVV!=jKRV1*b_z8$fwRE&+6Mh zM{b7J5Qob1rAHLzSA-!4+qjeY8!foh$u{Ly57)y^8%ckpX);cdkS0k$-KT|g2-E#@ z@=HZ)k%`#RQz73Lmb!rF5zs2EB`n;9&43e9Qp}(y^UZ`-AN7Q~VIWSxMFDrskeIv` zVT!;H8u^SoaSXSq$zh(lNiqt%|KuoGOQ!U2mNfg;n&A&wyd+txZ*`v@bB`#h^~e{z zCq-MBNL-p_eF1#*;1rkR$KNjr4MSGV+bY*bH0ZOO&CJa1Ll>lWq1Rt}QZ-2R7@q6ptEIn+rrcUO1H<8)f37n4)`R|JjEc^uOJOd3x@Tn&P965q#lSN|g+=lL{@ZWAMK{K_l2G1PMDwvQ2^qLb45=2O?Hy%L zCuBKLT`yj|h>M?c1U!v@EflBuPiynZ#8N9*9a0-@Bp3rx)~!drwWcBcpUJ2Y&Mbla zrbd9lAX+;9d}QG+P?0qXjgeOa_sMmF0~1hRH7V=PItq^IWTDV46aP&uF}d&+o4pnf zEop}HT0CU7NF>4)V0%+6@bXVyAx+PLP_T{rzG^c*H!m*+N*mq-^JpsNOBZFodWEzf zJm@`0>-GT!c?zbmcxB6lf9E?OQ`CAC#Na>|Mqo!i-v~X>>t27&|9YA?5PJqGFKP}y z?}$gE8?p3#%R=9C5_jzs;bIw+EK{5@GEkk(L*OL)K(rTP_d?*HlWO|iyhzX1smZiJNEsTeP|Xh0@;DkO7ipb zYsEHG(g}(P`{UiaW3r-kQ6qk~afQBJuW^sDPNe2-><|y@hB2e;CKe1+oU-dW!oNEb zcj9I4tLIl2365j#h?{T+L4{^(3IGIcIE=zI@_)dtu=`7%eTPl((wzu|^Be(H?kKt< ziLN%XzJ0DEQMCV-|vY#Lsf!upn0}4^r*Q4_K<{XvoJNZ^6kO9UUW2 zsH=d00mQxlOCuZF>kT1(df9`Vb^$G(~ep(8moYI z-VDHeb@vNaEiS&Z4knm6!a@)_A!>gVV%pzs1?ay8@f-zq1)AN=c17YW1nVJtRH!4| zcFMu^p?g6iQiEaa{($J|;Q?bZ^JAbS!K~<7zCx=&uOiMX@aF}+whKs~Yl}~ky<)cO zrI^_GqrO({LM*r_zc3cU^Q@!XOaFt|DQby7M0A%gee@)CUDPQLk3_j6&#qQD6f&%L zS=l}c$eS<+o_MmmUx;&mv~RGWQJPxp+eMLM((=U_#by8*w}eK#3?27D5;R%1GXd@ z*q3fW^6==RF316(QEabKILA2)(FjuSc;4^wk~(jx+$dyW#DA6zpwT9KRY~B$yPE+h z_f%@C$5_6YF+(ma6>C-tjA*m6C22QuO)HNwN#-8KpdOKJg5_u~!2RvROqDW=`N^d8{p_VH{wAJcMq2gZ*^Yf&z?g zoeXh{?f#vp_}O*$^sqaie|a5L4D^o}0WYAQ^8yIXBFhV_qA`8=VDgcxSFhS6H=><2 zJv}|w{4CH%`%isA`@Qzu%B-vbz9HdV7=>*TpEpYpF?V<#ns`7k`ZgQFSF^A&uGe2* z0)<_=lib6?#-b2`#8Uvz>Wr~f%Ui1mcxcjamEGOlYOt5bFkXSE=lA?``%weF`Nu9Z z>;Yv0|6k>fH9()y*KV9f7C#q z4Lq6v<6zK Date: Thu, 7 Apr 2022 14:30:56 +0100 Subject: [PATCH 2/3] feat: Flipper joint adjustment (#159) * feat(sandbox): removed FlipperTracingGame * feat: adjusted flipper anchor * refacto: adjusted anchoring points * refactor: removed unused import * refactor: renamed FlipperGame --- lib/game/components/plunger.dart | 6 ++-- .../lib/src/components/chrome_dino.dart | 12 ++++---- .../lib/src/components/flipper.dart | 7 +++-- .../lib/src/components/joint_anchor.dart | 2 +- .../sandbox/lib/common/trace.dart | 7 +++++ ...ic_flipper_game.dart => flipper_game.dart} | 23 ++++++++++++-- .../stories/flipper/flipper_tracing_game.dart | 26 ---------------- .../sandbox/lib/stories/flipper/stories.dart | 30 +++++++------------ 8 files changed, 51 insertions(+), 62 deletions(-) rename packages/pinball_components/sandbox/lib/stories/flipper/{basic_flipper_game.dart => flipper_game.dart} (74%) delete mode 100644 packages/pinball_components/sandbox/lib/stories/flipper/flipper_tracing_game.dart diff --git a/lib/game/components/plunger.dart b/lib/game/components/plunger.dart index b319af80..1911be02 100644 --- a/lib/game/components/plunger.dart +++ b/lib/game/components/plunger.dart @@ -129,8 +129,8 @@ class PlungerAnchor extends JointAnchor { required Plunger plunger, }) { initialPosition = Vector2( - plunger.body.position.x, - plunger.body.position.y - plunger.compressionDistance, + 0, + -plunger.compressionDistance, ); } @@ -161,7 +161,7 @@ class PlungerAnchorPrismaticJointDef extends PrismaticJointDef { initialize( plunger.body, anchor.body, - anchor.body.position, + plunger.body.position + anchor.body.position, Vector2(18.6, BoardDimensions.bounds.height), ); enableLimit = true; diff --git a/packages/pinball_components/lib/src/components/chrome_dino.dart b/packages/pinball_components/lib/src/components/chrome_dino.dart index 2caa40c8..327e14f5 100644 --- a/packages/pinball_components/lib/src/components/chrome_dino.dart +++ b/packages/pinball_components/lib/src/components/chrome_dino.dart @@ -23,7 +23,7 @@ class ChromeDino extends BodyComponent with InitialPosition { /// Anchors the [ChromeDino] to the [RevoluteJoint] that controls its arc /// motion. Future<_ChromeDinoJoint> _anchorToJoint() async { - final anchor = _ChromeDinoAnchor(chromeDino: this); + final anchor = _ChromeDinoAnchor(); await add(anchor); final jointDef = _ChromeDinoAnchorRevoluteJointDef( @@ -110,12 +110,10 @@ class ChromeDino extends BodyComponent with InitialPosition { /// {@endtemplate} class _ChromeDinoAnchor extends JointAnchor { /// {@macro flipper_anchor} - _ChromeDinoAnchor({ - required ChromeDino chromeDino, - }) { + _ChromeDinoAnchor() { initialPosition = Vector2( - chromeDino.body.position.x + ChromeDino.size.x / 2, - chromeDino.body.position.y, + ChromeDino.size.x / 2, + 0, ); } } @@ -132,7 +130,7 @@ class _ChromeDinoAnchorRevoluteJointDef extends RevoluteJointDef { initialize( chromeDino.body, anchor.body, - anchor.body.position, + chromeDino.body.position + anchor.body.position, ); enableLimit = true; // TODO(alestiago): Apply design angle value. diff --git a/packages/pinball_components/lib/src/components/flipper.dart b/packages/pinball_components/lib/src/components/flipper.dart index 49bd6d6f..64a82269 100644 --- a/packages/pinball_components/lib/src/components/flipper.dart +++ b/packages/pinball_components/lib/src/components/flipper.dart @@ -159,8 +159,9 @@ class _FlipperAnchor extends JointAnchor { required Flipper flipper, }) { initialPosition = Vector2( - flipper.body.position.x + ((Flipper.size.x * flipper.side.direction) / 2), - flipper.body.position.y, + (Flipper.size.x * flipper.side.direction) / 2 - + (1.65 * flipper.side.direction), + 0.15, ); } } @@ -178,7 +179,7 @@ class _FlipperAnchorRevoluteJointDef extends RevoluteJointDef { initialize( flipper.body, anchor.body, - anchor.body.position, + flipper.body.position + anchor.body.position, ); } diff --git a/packages/pinball_components/lib/src/components/joint_anchor.dart b/packages/pinball_components/lib/src/components/joint_anchor.dart index 98b80ece..7ca75ba0 100644 --- a/packages/pinball_components/lib/src/components/joint_anchor.dart +++ b/packages/pinball_components/lib/src/components/joint_anchor.dart @@ -12,7 +12,7 @@ import 'package:pinball_components/pinball_components.dart'; /// initialize( /// dynamicBody.body, /// anchor.body, -/// anchor.body.position, +/// dynabmicBody.body + anchor.body.position, /// ); /// ``` /// {@endtemplate} diff --git a/packages/pinball_components/sandbox/lib/common/trace.dart b/packages/pinball_components/sandbox/lib/common/trace.dart index d64a5c6c..2018b942 100644 --- a/packages/pinball_components/sandbox/lib/common/trace.dart +++ b/packages/pinball_components/sandbox/lib/common/trace.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:flame/components.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flutter/material.dart'; +import 'package:pinball_components/pinball_components.dart'; extension BodyTrace on BodyComponent { void trace({Color color = const Color(0xFFFF0000)}) { @@ -13,6 +14,12 @@ extension BodyTrace on BodyComponent { mounted.whenComplete(() { final sprite = children.whereType().first; sprite.paint.color = sprite.paint.color.withOpacity(0.5); + + descendants().whereType().forEach((anchor) { + final fixtureDef = FixtureDef(CircleShape()..radius = 0.5); + anchor.body.createFixture(fixtureDef); + anchor.renderBody = true; + }); }), ); } diff --git a/packages/pinball_components/sandbox/lib/stories/flipper/basic_flipper_game.dart b/packages/pinball_components/sandbox/lib/stories/flipper/flipper_game.dart similarity index 74% rename from packages/pinball_components/sandbox/lib/stories/flipper/basic_flipper_game.dart rename to packages/pinball_components/sandbox/lib/stories/flipper/flipper_game.dart index 78959374..d7f776e3 100644 --- a/packages/pinball_components/sandbox/lib/stories/flipper/basic_flipper_game.dart +++ b/packages/pinball_components/sandbox/lib/stories/flipper/flipper_game.dart @@ -2,13 +2,23 @@ import 'package:flame/input.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:pinball_components/pinball_components.dart'; +import 'package:sandbox/common/common.dart'; import 'package:sandbox/stories/ball/basic_ball_game.dart'; -class BasicFlipperGame extends BasicBallGame with KeyboardEvents { - BasicFlipperGame() : super(color: Colors.blue); +class FlipperGame extends BasicBallGame with KeyboardEvents { + FlipperGame({ + required this.trace, + }) : super(color: Colors.blue); - static const info = 'Shows how a Flipper works.'; + static const info = ''' + Shows how Flippers are rendered. + + - Activate the "trace" parameter to overlay the body. + - Tap anywhere on the screen to spawn a ball into the game. + - Press left arrow key or "A" to move the left flipper. + - Press right arrow key or "D" to move the right flipper. + '''; static const _leftFlipperKeys = [ LogicalKeyboardKey.arrowLeft, @@ -20,6 +30,8 @@ class BasicFlipperGame extends BasicBallGame with KeyboardEvents { LogicalKeyboardKey.keyD, ]; + final bool trace; + late Flipper leftFlipper; late Flipper rightFlipper; @@ -38,6 +50,11 @@ class BasicFlipperGame extends BasicBallGame with KeyboardEvents { leftFlipper, rightFlipper, ]); + + if (trace) { + leftFlipper.trace(); + rightFlipper.trace(); + } } @override diff --git a/packages/pinball_components/sandbox/lib/stories/flipper/flipper_tracing_game.dart b/packages/pinball_components/sandbox/lib/stories/flipper/flipper_tracing_game.dart deleted file mode 100644 index 482440cb..00000000 --- a/packages/pinball_components/sandbox/lib/stories/flipper/flipper_tracing_game.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'dart:async'; - -import 'package:flame_forge2d/flame_forge2d.dart'; -import 'package:sandbox/common/common.dart'; -import 'package:sandbox/stories/flipper/basic_flipper_game.dart'; - -class FlipperTracingGame extends BasicFlipperGame { - static const info = ''' - Basic example of how the Flipper body overlays the sprite. -'''; - - @override - Future onLoad() async { - await super.onLoad(); - - leftFlipper.trace(); - leftFlipper.body.joints.whereType().forEach( - (joint) => joint.setLimits(0, 0), - ); - - rightFlipper.trace(); - rightFlipper.body.joints.whereType().forEach( - (joint) => joint.setLimits(0, 0), - ); - } -} diff --git a/packages/pinball_components/sandbox/lib/stories/flipper/stories.dart b/packages/pinball_components/sandbox/lib/stories/flipper/stories.dart index 3cad3ade..3f802451 100644 --- a/packages/pinball_components/sandbox/lib/stories/flipper/stories.dart +++ b/packages/pinball_components/sandbox/lib/stories/flipper/stories.dart @@ -1,25 +1,17 @@ import 'package:dashbook/dashbook.dart'; import 'package:flame/game.dart'; import 'package:sandbox/common/common.dart'; -import 'package:sandbox/stories/flipper/basic_flipper_game.dart'; -import 'package:sandbox/stories/flipper/flipper_tracing_game.dart'; +import 'package:sandbox/stories/flipper/flipper_game.dart'; void addFlipperStories(Dashbook dashbook) { - dashbook.storiesOf('Flipper') - ..add( - 'Basic', - (context) => GameWidget( - game: BasicFlipperGame(), - ), - codeLink: buildSourceLink('flipper/basic.dart'), - info: BasicFlipperGame.info, - ) - ..add( - 'Tracing', - (context) => GameWidget( - game: FlipperTracingGame(), - ), - codeLink: buildSourceLink('flipper/tracing.dart'), - info: FlipperTracingGame.info, - ); + dashbook.storiesOf('Flipper').add( + 'Basic', + (context) => GameWidget( + game: FlipperGame( + trace: context.boolProperty('Trace', true), + ), + ), + codeLink: buildSourceLink('flipper/basic.dart'), + info: FlipperGame.info, + ); } From 9d2e9dd24327d5c72fa706929d142f4629c3f36e Mon Sep 17 00:00:00 2001 From: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com> Date: Thu, 7 Apr 2022 10:51:17 -0500 Subject: [PATCH 3/3] chore: trace all bodies in sandbox (#157) * feat: add traceAllBodies method * chore: update tracing stories * refactor: use for each * refactor: convert to mixin * refactor: simplify mixin --- .../sandbox/lib/common/trace.dart | 15 ++++++ .../big_dash_nest_bumper_game.dart | 10 ++-- .../lib/stories/dash_nest_bumper/stories.dart | 5 +- .../lib/stories/flipper/flipper_game.dart | 13 ++--- .../sandbox/lib/stories/flipper/stories.dart | 4 +- .../lib/stories/kicker/kicker_game.dart | 13 ++--- .../sandbox/lib/stories/kicker/stories.dart | 4 +- .../lib/stories/slingshot/slingshot_game.dart | 53 ++----------------- .../lib/stories/slingshot/stories.dart | 4 +- .../sparky_bumper/sparky_bumper_game.dart | 14 ++--- .../lib/stories/sparky_bumper/stories.dart | 4 +- 11 files changed, 38 insertions(+), 101 deletions(-) diff --git a/packages/pinball_components/sandbox/lib/common/trace.dart b/packages/pinball_components/sandbox/lib/common/trace.dart index 2018b942..8585169e 100644 --- a/packages/pinball_components/sandbox/lib/common/trace.dart +++ b/packages/pinball_components/sandbox/lib/common/trace.dart @@ -24,3 +24,18 @@ extension BodyTrace on BodyComponent { ); } } + +mixin Traceable on Forge2DGame { + late final bool trace; + + Future traceAllBodies({ + Color color = const Color(0xFFFF0000), + }) async { + if (trace) { + await ready(); + children + .whereType() + .forEach((bodyComponent) => bodyComponent.trace()); + } + } +} diff --git a/packages/pinball_components/sandbox/lib/stories/dash_nest_bumper/big_dash_nest_bumper_game.dart b/packages/pinball_components/sandbox/lib/stories/dash_nest_bumper/big_dash_nest_bumper_game.dart index 9638c36d..649256d8 100644 --- a/packages/pinball_components/sandbox/lib/stories/dash_nest_bumper/big_dash_nest_bumper_game.dart +++ b/packages/pinball_components/sandbox/lib/stories/dash_nest_bumper/big_dash_nest_bumper_game.dart @@ -5,10 +5,8 @@ import 'package:pinball_components/pinball_components.dart'; import 'package:sandbox/common/common.dart'; import 'package:sandbox/stories/ball/basic_ball_game.dart'; -class BigDashNestBumperGame extends BasicBallGame { - BigDashNestBumperGame({ - required this.trace, - }) : super(color: const Color(0xFF0000FF)); +class BigDashNestBumperGame extends BasicBallGame with Traceable { + BigDashNestBumperGame() : super(color: const Color(0xFF0000FF)); static const info = ''' Shows how a BigDashNestBumper is rendered. @@ -16,8 +14,6 @@ class BigDashNestBumperGame extends BasicBallGame { Activate the "trace" parameter to overlay the body. '''; - final bool trace; - @override Future onLoad() async { await super.onLoad(); @@ -28,6 +24,6 @@ class BigDashNestBumperGame extends BasicBallGame { ..priority = 1; await add(bigDashNestBumper); - if (trace) bigDashNestBumper.trace(); + await traceAllBodies(); } } diff --git a/packages/pinball_components/sandbox/lib/stories/dash_nest_bumper/stories.dart b/packages/pinball_components/sandbox/lib/stories/dash_nest_bumper/stories.dart index 95f3cd2a..22f792bc 100644 --- a/packages/pinball_components/sandbox/lib/stories/dash_nest_bumper/stories.dart +++ b/packages/pinball_components/sandbox/lib/stories/dash_nest_bumper/stories.dart @@ -8,9 +8,8 @@ void addDashNestBumperStories(Dashbook dashbook) { dashbook.storiesOf('Dash Nest Bumpers').add( 'Big', (context) => GameWidget( - game: BigDashNestBumperGame( - trace: context.boolProperty('Trace', true), - ), + game: BigDashNestBumperGame() + ..trace = context.boolProperty('Trace', true), ), codeLink: buildSourceLink('dash_nest_bumper/big.dart'), info: BasicBallGame.info, diff --git a/packages/pinball_components/sandbox/lib/stories/flipper/flipper_game.dart b/packages/pinball_components/sandbox/lib/stories/flipper/flipper_game.dart index d7f776e3..5a9e1787 100644 --- a/packages/pinball_components/sandbox/lib/stories/flipper/flipper_game.dart +++ b/packages/pinball_components/sandbox/lib/stories/flipper/flipper_game.dart @@ -6,10 +6,8 @@ import 'package:sandbox/common/common.dart'; import 'package:sandbox/stories/ball/basic_ball_game.dart'; -class FlipperGame extends BasicBallGame with KeyboardEvents { - FlipperGame({ - required this.trace, - }) : super(color: Colors.blue); +class FlipperGame extends BasicBallGame with KeyboardEvents, Traceable { + FlipperGame() : super(color: Colors.blue); static const info = ''' Shows how Flippers are rendered. @@ -30,8 +28,6 @@ class FlipperGame extends BasicBallGame with KeyboardEvents { LogicalKeyboardKey.keyD, ]; - final bool trace; - late Flipper leftFlipper; late Flipper rightFlipper; @@ -51,10 +47,7 @@ class FlipperGame extends BasicBallGame with KeyboardEvents { rightFlipper, ]); - if (trace) { - leftFlipper.trace(); - rightFlipper.trace(); - } + await traceAllBodies(); } @override diff --git a/packages/pinball_components/sandbox/lib/stories/flipper/stories.dart b/packages/pinball_components/sandbox/lib/stories/flipper/stories.dart index 3f802451..f8aa0075 100644 --- a/packages/pinball_components/sandbox/lib/stories/flipper/stories.dart +++ b/packages/pinball_components/sandbox/lib/stories/flipper/stories.dart @@ -7,9 +7,7 @@ void addFlipperStories(Dashbook dashbook) { dashbook.storiesOf('Flipper').add( 'Basic', (context) => GameWidget( - game: FlipperGame( - trace: context.boolProperty('Trace', true), - ), + game: FlipperGame()..trace = context.boolProperty('Trace', true), ), codeLink: buildSourceLink('flipper/basic.dart'), info: FlipperGame.info, diff --git a/packages/pinball_components/sandbox/lib/stories/kicker/kicker_game.dart b/packages/pinball_components/sandbox/lib/stories/kicker/kicker_game.dart index 21c0cfb8..1b29c3f9 100644 --- a/packages/pinball_components/sandbox/lib/stories/kicker/kicker_game.dart +++ b/packages/pinball_components/sandbox/lib/stories/kicker/kicker_game.dart @@ -3,10 +3,8 @@ import 'package:pinball_components/pinball_components.dart'; import 'package:sandbox/common/common.dart'; import 'package:sandbox/stories/ball/basic_ball_game.dart'; -class KickerGame extends BasicBallGame { - KickerGame({ - required this.trace, - }) : super(color: const Color(0xFFFF0000)); +class KickerGame extends BasicBallGame with Traceable { + KickerGame() : super(color: const Color(0xFFFF0000)); static const info = ''' Shows how Kickers are rendered. @@ -15,8 +13,6 @@ class KickerGame extends BasicBallGame { - Tap anywhere on the screen to spawn a ball into the game. '''; - final bool trace; - @override Future onLoad() async { await super.onLoad(); @@ -31,9 +27,6 @@ class KickerGame extends BasicBallGame { ..initialPosition = Vector2(center.x + (Kicker.size.x * 2), center.y); await add(rightKicker); - if (trace) { - leftKicker.trace(); - rightKicker.trace(); - } + await traceAllBodies(); } } diff --git a/packages/pinball_components/sandbox/lib/stories/kicker/stories.dart b/packages/pinball_components/sandbox/lib/stories/kicker/stories.dart index f4a6bf91..77d6ff29 100644 --- a/packages/pinball_components/sandbox/lib/stories/kicker/stories.dart +++ b/packages/pinball_components/sandbox/lib/stories/kicker/stories.dart @@ -7,9 +7,7 @@ void addKickerStories(Dashbook dashbook) { dashbook.storiesOf('Kickers').add( 'Basic', (context) => GameWidget( - game: KickerGame( - trace: context.boolProperty('Trace', true), - ), + game: KickerGame()..trace = context.boolProperty('Trace', true), ), codeLink: buildSourceLink('kicker_game/basic.dart'), info: KickerGame.info, diff --git a/packages/pinball_components/sandbox/lib/stories/slingshot/slingshot_game.dart b/packages/pinball_components/sandbox/lib/stories/slingshot/slingshot_game.dart index c02689ca..8d54f391 100644 --- a/packages/pinball_components/sandbox/lib/stories/slingshot/slingshot_game.dart +++ b/packages/pinball_components/sandbox/lib/stories/slingshot/slingshot_game.dart @@ -1,14 +1,10 @@ -import 'dart:math' as math; - import 'package:flame/extensions.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:sandbox/common/common.dart'; import 'package:sandbox/stories/ball/basic_ball_game.dart'; -class SlingshotGame extends BasicBallGame { - SlingshotGame({ - required this.trace, - }) : super(color: const Color(0xFFFF0000)); +class SlingshotGame extends BasicBallGame with Traceable { + SlingshotGame() : super(color: const Color(0xFFFF0000)); static const info = ''' Shows how Slingshots are rendered. @@ -17,50 +13,11 @@ class SlingshotGame extends BasicBallGame { - Tap anywhere on the screen to spawn a ball into the game. '''; - final bool trace; - @override Future onLoad() async { await super.onLoad(); - - final center = screenToWorld(camera.viewport.canvasSize! / 2); - - final leftUpperSlingshot = Slingshot( - length: 5.66, - angle: -1.5 * (math.pi / 180), - spritePath: Assets.images.slingshot.leftUpper.keyName, - )..initialPosition = center + Vector2(-29, 1.5); - - final leftLowerSlingshot = Slingshot( - length: 3.54, - angle: -29.1 * (math.pi / 180), - spritePath: Assets.images.slingshot.leftLower.keyName, - )..initialPosition = center + Vector2(-31, -6.2); - - final rightUpperSlingshot = Slingshot( - length: 5.64, - angle: 1 * (math.pi / 180), - spritePath: Assets.images.slingshot.rightUpper.keyName, - )..initialPosition = center + Vector2(22.3, 1.58); - - final rightLowerSlingshot = Slingshot( - length: 3.46, - angle: 26.8 * (math.pi / 180), - spritePath: Assets.images.slingshot.rightLower.keyName, - )..initialPosition = center + Vector2(24.7, -6.2); - - await addAll([ - leftUpperSlingshot, - leftLowerSlingshot, - rightUpperSlingshot, - rightLowerSlingshot, - ]); - - if (trace) { - leftUpperSlingshot.trace(); - leftLowerSlingshot.trace(); - rightUpperSlingshot.trace(); - rightLowerSlingshot.trace(); - } + await addFromBlueprint(Slingshots()); + camera.followVector2(Vector2.zero()); + await traceAllBodies(); } } diff --git a/packages/pinball_components/sandbox/lib/stories/slingshot/stories.dart b/packages/pinball_components/sandbox/lib/stories/slingshot/stories.dart index 6e985d32..70dfa021 100644 --- a/packages/pinball_components/sandbox/lib/stories/slingshot/stories.dart +++ b/packages/pinball_components/sandbox/lib/stories/slingshot/stories.dart @@ -7,9 +7,7 @@ void addSlingshotStories(Dashbook dashbook) { dashbook.storiesOf('Slingshots').add( 'Basic', (context) => GameWidget( - game: SlingshotGame( - trace: context.boolProperty('Trace', true), - ), + game: SlingshotGame()..trace = context.boolProperty('Trace', true), ), codeLink: buildSourceLink('slingshot_game/basic.dart'), info: SlingshotGame.info, diff --git a/packages/pinball_components/sandbox/lib/stories/sparky_bumper/sparky_bumper_game.dart b/packages/pinball_components/sandbox/lib/stories/sparky_bumper/sparky_bumper_game.dart index a0ad661a..a57beb8d 100644 --- a/packages/pinball_components/sandbox/lib/stories/sparky_bumper/sparky_bumper_game.dart +++ b/packages/pinball_components/sandbox/lib/stories/sparky_bumper/sparky_bumper_game.dart @@ -5,10 +5,8 @@ import 'package:pinball_components/pinball_components.dart'; import 'package:sandbox/common/common.dart'; import 'package:sandbox/stories/ball/basic_ball_game.dart'; -class SparkyBumperGame extends BasicBallGame { - SparkyBumperGame({ - required this.trace, - }) : super(color: const Color(0xFF0000FF)); +class SparkyBumperGame extends BasicBallGame with Traceable { + SparkyBumperGame() : super(color: const Color(0xFF0000FF)); static const info = ''' Shows how a SparkyBumper is rendered. @@ -16,8 +14,6 @@ class SparkyBumperGame extends BasicBallGame { Activate the "trace" parameter to overlay the body. '''; - final bool trace; - @override Future onLoad() async { await super.onLoad(); @@ -38,10 +34,6 @@ class SparkyBumperGame extends BasicBallGame { sparkyBumperC, ]); - if (trace) { - sparkyBumperA.trace(); - sparkyBumperB.trace(); - sparkyBumperC.trace(); - } + await traceAllBodies(); } } diff --git a/packages/pinball_components/sandbox/lib/stories/sparky_bumper/stories.dart b/packages/pinball_components/sandbox/lib/stories/sparky_bumper/stories.dart index d0933b67..1a5f8801 100644 --- a/packages/pinball_components/sandbox/lib/stories/sparky_bumper/stories.dart +++ b/packages/pinball_components/sandbox/lib/stories/sparky_bumper/stories.dart @@ -7,9 +7,7 @@ void addSparkyBumperStories(Dashbook dashbook) { dashbook.storiesOf('Sparky Bumpers').add( 'Basic', (context) => GameWidget( - game: SparkyBumperGame( - trace: context.boolProperty('Trace', true), - ), + game: SparkyBumperGame()..trace = context.boolProperty('Trace', true), ), codeLink: buildSourceLink('sparky_bumper/basic.dart'), info: SparkyBumperGame.info,