From b2227e83105bb8cb2b344afc7f7cd530b33db9b1 Mon Sep 17 00:00:00 2001 From: JamesA Date: Thu, 16 Jan 2025 00:06:57 -0500 Subject: [PATCH 01/33] Putting the correct midi in the file that jukebox is looking at. --- assets/bigbug/music/BigBug_Boss.mid | Bin 33150 -> 13021 bytes main/modes/games/bigbug/entity_bigbug.c | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/bigbug/music/BigBug_Boss.mid b/assets/bigbug/music/BigBug_Boss.mid index 37cfe603033872043ec8cfc6b272ce6a7a1b46a2..67641969fddd061fbdfcedc91d8129c1c3bc0d85 100644 GIT binary patch literal 13021 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 literal 33150 zcmeHwTT@$Emgf3^b2vv=BqSt+#8rR*0|Eq?i$Q>8VOd6&Wn*k(a2jlEjKNOQr0v8> zNUrLdTTgUl+BGpx9WfE}GH+v>w~6SU>A5`4Z|JlgF>mt|YQBB2(^=J-T@}^SS=n8B z+FEPxv(G+zughNFTE=HD++G(Emq6^5e|`4Cr*}!!%CDEIsw#qp{5oH$|A$rib-x^S zxzEX8EFH+=u59ef`XjlqBdfde*WRCH|6)l(7cQ+YiWC-X8#12HIWNfIx|q2!`wh_w z(zh!5*>P812D0fuPNp+c+F6;I&uCdWlg?@xNv0C|yo}5Zy3>-H8Pifym=jxaXl_RA zQ)B6*I3|zJOi6rV$UiCd6Y}R(*_>p~r?b+Qk>X-*O!7G?oIjBhPhM&YQoONvQ3^T9 zUzA+-)}ojNvE{{{lj3A%Lb91;Mw+rx{G@PN@(Z$fUUIo~LA1P7ElNE{UCLgT%;H>D z>N)-T;;Iy`Oa7MRF3m2Aryx~%K~DZ{EW5%{C$kb%qY9@aHz$kdB%dEHNKH=a7uAf# zYwGdyl3$VB^2tTf3*yX6BVTeRyC#{dcd}B+$1fDPjw_PCBDo7w1u+)IpXUZjaXE8G zvb&oZso;VyEMAhrhU7OSx7xKRtp!0|qFhQ%c3U!=J6WmXTxS<2`PovqEcvCLMQO`P zRY7zv<+l3XyeuvseXlz&-bJ<2;(X?^WY<0$=~2o5(Nn_N=07W9JQL= zmCT(}S!w1=uHJEw_HHur2vbS$rtD2c;_o^{{PGc zk7=n)F$T^gC0mdbqs&W^UKz+pEGvwo{W ahkzZih<;gq#ww_!Lz=MI8$QF3ewWB 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 diff --git a/main/modes/games/bigbug/entity_bigbug.c b/main/modes/games/bigbug/entity_bigbug.c index 63a22d0dc..42360cb96 100644 --- a/main/modes/games/bigbug/entity_bigbug.c +++ b/main/modes/games/bigbug/entity_bigbug.c @@ -4295,7 +4295,7 @@ void bb_onCollisionCarIdle(bb_entity_t* self, bb_entity_t* other, bb_hitInfo_t* { bb_setupMidi(); unloadMidiFile(&self->gameData->bgm); - loadMidiFile("sh_revenge.mid", &self->gameData->bgm, true); + loadMidiFile("BigBug_Boss.mid", &self->gameData->bgm, true); globalMidiPlayerPlaySong(&self->gameData->bgm, MIDI_BGM); // close the door and make it not cacheable so bugs don't walk out offscreen. From 44b0d0f92d2ce272f4669a8aec7a52b919c0fa9c Mon Sep 17 00:00:00 2001 From: JamesA Date: Thu, 16 Jan 2025 00:29:31 -0500 Subject: [PATCH 02/33] bug fix in regard to fuzz testing --- main/modes/games/bigbug/entity_bigbug.c | 1 + 1 file changed, 1 insertion(+) diff --git a/main/modes/games/bigbug/entity_bigbug.c b/main/modes/games/bigbug/entity_bigbug.c index 42360cb96..5cb043510 100644 --- a/main/modes/games/bigbug/entity_bigbug.c +++ b/main/modes/games/bigbug/entity_bigbug.c @@ -1490,6 +1490,7 @@ void bb_updateBugShooting(bb_entity_t* self) { if (bb_randomInt(0, 100) < 1) { + bb_ensureEntitySpace(&self->gameData->entityManager, 1) // call it paused and update frames in it's own update function because this one uses another spriteIdx. bb_entity_t* spit = bb_createEntity(&self->gameData->entityManager, LOOPING_ANIMATION, true, BB_SPIT, 10, self->pos.x >> DECIMAL_BITS, self->pos.y >> DECIMAL_BITS, true, false); From ace2c02f5c148b4cdc736e0772eb2f96b40f7e41 Mon Sep 17 00:00:00 2001 From: DebrisHauler Date: Fri, 17 Jan 2025 22:30:05 -0500 Subject: [PATCH 03/33] updated menu graphics --- assets/bigbug/menu/bb_menu0.png | Bin 4558 -> 5145 bytes assets/bigbug/menu/bb_menu1.png | Bin 28105 -> 28477 bytes assets/bigbug/menu/bb_menu3.png | Bin 2249 -> 2375 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/assets/bigbug/menu/bb_menu0.png b/assets/bigbug/menu/bb_menu0.png index d47a023c6ff8e5ecf175eb228f33d582e8f100b5..34c98657e30277ae5518e22105632df2b8c9c99d 100644 GIT binary patch literal 5145 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 delta 4552 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 diff --git a/assets/bigbug/menu/bb_menu1.png b/assets/bigbug/menu/bb_menu1.png index 890258a06e138ba42c9acb1c96380768e230b068..0eddb7cdbdd96ca8c0936aec5c01c2df90c18d1d 100644 GIT binary patch literal 28477 zcmV*0KzYB3P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>DZqi9aK~#8N?Y#$l z71h@M|2^rw_uhL*AOsS6@4X3#U;|V@L9rm%yI!y$MNw1~q>1!iLk%SmLI@$G_uhLs z|1|;bd*A!J0*c(%|GjxWgfnN(>^-}z{j6ur?AfDItJOR)Ri&OZ5o+t9**So7SMBLz zeF$wmsB463%K*xAA}G_-rKM~Wm$w)3{f}xD{|0!vs?>94Lqr6W7sJu&fp~kS(XK3! zqnANP7h+n5U`;d08hU#z(>OMW) z21@`(shna|nktHuN;&_<`vm-LIZxI;!@QRtEWtcJ zQl(A{bL9HZrkMEqQF8tq`2kM!2o0t1dI7fH7Fae}Ql(o(T1_>nzZ}HY*N?)oON_QG zWZk|t6>~p$;t7>HCkJe;KwA^C^1<61l9F!j(~#CJDg*~7g!J3mx()v?R2z4At5?-5 zr2PC|&aU5&v%fW&SDOimuq3vo3XcL&1y)sL7}Q`MlR|bzxmcS4OWv(y-uwq^R6`_m z^9-KLnR-2H)Yr*ea|%q1X>4r9+S3xlW@Bz#xF$qnOZJs4jC`!AI(-$lAbmV-YB_kQ z&FKFKJl$35XTKgo)a7RShUl?sLjko;6X{s_HUa%d5c}nKq^g~$>$ZTF)2GlgOyjkA z1-v~Wi3_Q1ZQh3efojp(iRyP(UPU*(3bz0QJo*hFR+K=IyE(eMuA=rZr>a6orL+aR zNPjv#H64QnJ?1-%CoJN@8q*U)ROV5X*2deg1;?RslQxnM*5X9TzmE>a@Isjb|rD;=w=Mf zEHHTD3Bue=Ir_mW2E8$x1N%0RRO`cXueW*n*|s)s!~Z~|qyHtWORMqd5GrKi!Qmyp zkW*Dk!@4sBk2b-7L@2`_=?V4CG}bp#b@@0~GBPMAxRt{LM7}xnE7?T5p@V5@G^ENv zoBUlTi2Hdr%@wJH_Ub_7NM{^fJ)yY}jWP=^Ub;wWQ6-k8w=!wN??!Jgq3qs|FmU~M zG?kRIe)JRUzwk9VH*QckzCGg_nkcWxq|Bm>V@tmz>Erd-R_D>!a4Um0wBZ&s4yCxD zS=>lTVImnPYt_1e#L%FI9RZa$3y5ZO~-^!#7 zzZ+Zh`r%wz#HAUt$c{Tpm!(hQ5Yn4&pHHXk_4RzX>j!>$`2(8kbV)sQ0pBUpaITXu zs_|9^ZD_+SXapO$;W%wDfnkH``ovf!y*i9;kB4$S*N}$BDypu5`?&Vx9XL;UZ3Ry~ z_Z)Sdx>9Cx{Zm;tK#XAeMC)NC;rAc zd@QYaWcqBb#T}zjL`@t17$PX)+U_%0b{|N;wXdVTUPR8XQJnbZF!tT-Fl{Qs!Oep5 zq!OIwj$nxNfzK1;3G}^{K^xj|3#v`K{nX<}q@(MgO?_@N`klS0jm^Y3&<@S|0?OPx z2=dkA%7&|q>Cl(@Swq;y8YR?`T$xHjx?o}V%okvMtQ{) z=jYRT-Y8tE3)pP_LE`N)D@zH+OSrPX0KJArw7jg*FD|5gcYjtKd;C@w4-Tfq5D-zv zxIN49?Eer(-MgbIDkb|$I;N36^fE9YD!z)^V;3Ag=^WS4tYb&IZPi37BL}g%FRSq#pO#Ai?j%1`!UtCP+qwnxs zm({njcyQ3P5Xb1@O>+_G96v(Uro)tpe8y`DCoV!BAvppCw;1Pb={58FQ zT1wxFEi{a5qoO$V<(I@INEqZ{icw+{e$Nh-vT2|zLiC51TfxLO=Qn*XVRBhc>j~CNwH&hq<3O9%Fma`Qb+ie5@Z_i-qJ;@KnMj zA(%={{A_GFQJF=%5C=*{$!Ml(-OA#@!AZ#PK#?0weS0!z#e3KUbf+f70FTR+_;&Bc z<0BtqXh%>1Kxj{?3NM|uI4eJZ{OA_ zycxT%P-#$sd7vFtOD++%Z~*V^If}ilKVAEd<$O}stsL6WhMUl^G`~ziQaRcgwUoph zpt7L`Q)>eV_k`0|3AWO}SKEN54z5&ux&v2lFE(zw$g=SlZe{V{uxhJX^+e!70?!}j z$j{rTKNC%;>o76=Iufr}a$@OLR_)r(`1h9c^$EZ5JE=DC|W2 zoqc~3k^TD8Vrs~mJyER}(Ft~jOJ$TEN)`L?!Y8aVZ!dZn-vORD42{IBvG!IDZD_+y zsD9q*tA6>1FshFn!Sb1bTz_XRSlfVkGiX+Ul`URA4!E>yNBrh9wCHM4TdK#|Lk;+w zoOnqrBZKh+|M3Sv_;lh1b1*DCOSz-rd+~QFJYiM(9_1|QZ~V#EaFUTGOs*3kKF6y zST-iEt=-%3cc>&9QonTaCrl&M)St*<jUPsE669Er>3?RGCQCaAE@Cfe|KXe7D6%JvtZX8Y*5L~Y&9TklQB&BveelY1du zq;N_B6MvdRqqPIEl(OTUF9;YpkW8Kax3Xx%e;R6Qnz3~XM0e*2(zL43C@sa*Ru7uY zL^jqE^GiB|UhGTI*db)BKgFpn2Z;ZC9fMt!XTsXhhW~-8rWCdPqzTx1*wXvS{_Oqn zJ(>>`kyVr{a@bv2xd}{~prVQ{g9hO;d^nfu8(0$jCIdrr+q~6(AP$^WsYmv*A@Yr} zq+Kti^oqEaY6**UbR;&FFsa@MJ0oL^T}>#BDWUI+Ptc@iMWlK&FAV#&%_iFLcWC5^ z)A987!c0}crb)ACI^RT=o1J*77BEsnPXq9A;Q3FV$2!!T^$$IcE>^gEw#~)=Plbbd z2ReN8HpbSD^m=>@-6wZv@3+sQ7c90=VF^(;FxS)|q;n5OteVM=kH4WNN6nbNIk$3X zLmO^Fqsp`sg;p&%gjv$!;s%B_m|jT&Z!sj55KO)WUESSDsmmsM)z4sN&c&$@aXKaC zRu*mePeYkS6GZ)dM|sC!iJb6rD9T@#>pK=**bj<3<%@l zeVceWKa(*!5<<1%KN6vOmnpa!N8HBsSTr|NWbIDU_DfKs3APd{^@-%VnI55u&CI{F z5}VF`(D14_fmKE}F1`B@>=VG5(`TrwZGvMrXf|}A!peglGY5dZ zDYYf#s4vH|e(ydsly#ok@E-}4a7f3um+*Y{UJO0Wh@bNnSS!=2tH9h08j7K~nr@E- zaq^vY#DDk`X?xG$;UNmR4Q==bXdEvKK(nQeq#KFsJ+hvJ)CM?tjdrgLx_Lnljn)OJ z2y|x9>`B}>dx7>%dZcQF-^`&6|8Y1`{vxTRF|6)1k<*`VVesl#a2eGedV51cDdvF| zbPo1o^-rg`d~hFri{8XjLyH^fZP#Gh@V}wjUiiHF^{=c5Hm@RA*Rb{VAGuf0aO%`m z8Y)G}WY!b;_DD{@yMf88-p9qnjhzLByy5>m<3dW??il!18b)1yW(xzHo>tC{{z*P^6u&&)00?u z+E7y4fVHn9g$3D|*xRD1Zl=`4oIpQa;(tzHc)RX2KR1%yFML6D)CCNT+l=Wy5i8=K zQZF2F01cHk%|#{HP3^(8bJs8`E=I$snF<3}7QN7mcVGL8pi$GgvT`~4W2TTl?;DK$ z+iao@{{sz=+~W+2K26ZyKBoVt3-RpYA-fGwCl^wxEGbu9O8lMz^jn%S33kJWsc{v<-_;s4Jtsp*+%H}#ao`p%77}TX?rmcE| zz1I_HC@v)Qv3Gf)>vy-ZXv2RRoU48%RnL@|AAiKf(3!4N190lpo%mxyD&2gLC>m{X zHi)z7=h~GJ6Ib@d9pX9PUvA~lhBn-UDy10pV=v6**uiZQR~N&z3K$gxSH+4-%OSu( zxz3AeSrx;cd79J3DuzuSOKsw0zV3IaO%DGD;#6uU_25@*2(q_9yS$3i(PwGM%@qX` zPH{#fRjNV^!!4=)?2_0<6CGZAg4C<|ocyW;fAf=VHqnN^L!+{^H@dxi37*-XLC?*l z)6-*MYod6kR0uN#V>8*-B*4L*>rYbEl!bY)gOJNbI=(iTw|5`G*4mFQeaCY#t!?sU8~!&m{EUup{aiLW z>D3fmI!|3$10F$k5Z)2?CJ-PT6JTIWb4PbdKi-Oqrze}Y#;|k9)Qh3yOSly1#MO)v>V5Puvb00b+m#<@%t!CoVG=dE-O8d3 z|7j@6h9?iN=geyh(8_LRK%Zdf=?W*VLVI^`kg(YTj?MXu&dX{T}f4THoYefrPHWBJhF2Q z3DGBRWzmNJG}O3;vvI-4Sd8w^sNvm-IuZ+qC2WrHgZS%&5AlK{IT#ArRA*jh>XcFZ zeCZSe1KK9qwc&q5wY_qodW6lVm{{tf>*mEXA3a3o@pxAMO=Pj5C8QUl`)EIUIN5N+ zQlHe7(e!$3D8^kL=Y8ujXt`OFklC9s%QrA>f~&PXIMvXO=N!IlT~5YPY%zsiq-AzN z)760d#LtQUzL1X=-`jkN&S5I`f)}B8AJEl;r76$R&HOy=tWKi5z1Fc@B00-<(W0K~ zDYl<2$lNv*QG%u)eRX-zP?c3B0s1>OU`>EL865K`6|E1$AiE8 zu533pOZ}4Mco}qiaoo%7 zn>2^O1rtdB+a|PiYRIw@*>YYQr&9m}2l}Ju?oad=KVv&|II;UKQ+?zx21b9~dbQ!- z4^?d$YMTk;v2nMi|5Jn6{o{Ky?a3pjBp)L~N183G@NxAZ_Gm0-W-hdN7||F$gn6C4 zDe>;Y+95;G^>e4j+=WNK{*cS>eu0y!k@X*ZPQXLsNi8lTz{i~nYgg0h;YV=k)(f>p z3^s?~W%*ZkH^#iYP__N?aSXDv#3G^t9tr8ZyYM;mnoMbOa3-izM>68h%8@O2`3Dd( z?*)2hMeuOnf_pafFJh;v=e=uAzXAP7)yu&6>rKS?wa4dr7Ir}oN|HtfR^M*oTwr_rlXA((PNh0g%4c9GXObe- zNPE8jJa$nuD=6TctMb=)>?OPm-K-oyKw* zm(k~?>C|hQ5TV|}OCz?n5lkEY4wZOc^~A5-!I)V`55F z5>;5s#e+9!uF)ZD=QenJ6yv)1Q(vx0`uXdaT4|!u(n8|y(-?SoQaZFNx=Ta>O`p$) z1G^Dbf0d$g4OB@9YM~bH4xi|RPp~eR*Pf%ayDQ=C z0@2DS#dYW?!tC_PGHwusq{X56FVQhFocN+k%(A`A8=u{M08bdJQWu)Q(eH15j}XE< zV`|PjPd>rUrC(6rF`OQqZ295*Is7wpIAd}eR;6J*HW+8O!K58MOv%B$HxmoOCH*(Pa|LHkSE==8EG6q)4GD%#)V{ZtVs}%Fnwrq;;Ebz*DKR_F z(ayz<=;Y(rd4-d=^&qb8%^5bNlrQhWxCRYZsr!!z&`dS;EaeZ*}3**U=L7Yw2q2lzXJT|olXY=AXe5pG|MmY>M zibi>SHvgyNoR4^0$e@uMyAH5!{~)xEzR8fg*h56PNiqEGT1AuQQxGcmM5t`}}rPb1fm8El0Xz)?Vt> zaE`3(A}(%-C%@CHGO7|t-d)0q&)iXEeb0^HbJ(jIFg<)#vq_IdpOnn$*KBvkJA3 z8M!T5G{mIg^K5t0zdDPljR6H&6+%`j>;k&d|M_Qeun%B?kt3#Ncdpp8PpQ4kDO~MwD`MGc{CUGg&2hHz*NfD;6(2)o+7}rBLhv=a%|Z${`SG$8}8*H zD)q-T68=<`i5o4zc6cbwRgMfB>cjWneT#d$08)ixQH!g(5Y4n*FLPB~mj0UKL~S_( zR*H)d(liy-SCETKI}0+LdSiVx0gV6+42nxg#Y-{};)%|u>-_P!y4YbS19$(4b;SMp z;5zq@qN+-zHZ~SlDLN!zKo2y8fUhjtEMMy|wllj8lKJJBO8wz;x}1-#$Lo>NbY1ug+WQZ)Pr`2l@2>dG9E+=j&CwsdPmj0W zV%OpyNU*Tre6N@25PARYeEa;@vMd?D@;z##UipWva7~nZy|)LWhYTYlFA*&v+=33K zlpni7cbhm)?mo};Q+Gc9r(dene_L)$PGThv5nbu}{Nt1!NG37rS4^yI(Rp|ho>rwq zuQ*J#zcnQ%Pl-&iXTiLAtlGZ<>na-(cI<%q>elpKo_~U}L($wgc9H^vTG|cmMx&84 zjk|VXID8a&dv{YJgfpk}FTC^4gR?<|y#-jwQVqS-u6!CMC1&(REhxHm>4 zgDDXmeBg=Ym<;SdM9<+|*Rdgf)MFS}+__>;i-IZA4Wq*4A+pYHrOu-xDW5DCy0<}D z+oK6G;KFxFH*RK>{ZaUFFCTn+_l6rfN2T_?Djh5`eZu-Jq-Y%G z{Dd!sIP7qK(v_y2sd&un-Fp4y%JyrN@4ncY&MmS#X~H?ZX1H_o@indc9Ho7-m1`9l zqLu6GaDK{DIy4>k{_f)8w8%`n!h(n2U`O3Wx;EEPym1qot~|KT{iCQYQK`Fr_9?xe z^5^u&t2q7nFW@e^JGBt~X&vdKw&hA)73mkx)3eubj;&e`qxutUp^x|DUGdGj}97lUeT^m`Z*X&ue7w|=`l>XM|d zcCV&?Uk7$9c$tEou|i~yBCWEq`{YfQ_vp@X#{yQ){+#yC_MBS!EABI&W9g*N8QSyy z+xhnS-QDv!HgbU|T4SkKC6+zAGHT>x(i*QpN-akV;&8c=E@NE>r``_OYn);0>N}tR z!mlcIpXj-C?i5Vi+snv{jgj_}v2?W@!5RI>5~kC z9`BApVmeOaMw21&Rn~?xen7RLxY<+)iqhDt?j8_f0~e22a|N+IyDvAIJW4KU6(47-%kph z-a5NC*ujJ%Llbguh;~j^0#GTVjZ#h-`EhCB>yN%!4Yk#pL=N>ArD00nA(32OxdV&f z0et-QCW1^)+_7Scg4tX!mHLJ9ll;Jiqgd1lPX((-B=!<=6E;Pc# z-I>(w@$`SC2P}P(_g3A#;a(mk3g%iz5g5`T3ao5|AVqPUK1HuZ-8i~Uj7DGTl&Ohh zS9?5!RF400mbYJyER z|9XL-S$6my&13UlkYEm^yrh2U(|lSsZ=*b~oWZMKCH`6r8E0=uyOdxt*pZT?QjC2a zP+v;Hee?h5pYiBjpuUVs#n8#ht>ta5QC4xh3FzS(i-^S`>cuX^Kp6Uw&kp}3-+E}zdJ^OCe>pc!Xg zT93)#5Gu~6qvLCbc2*{SgTv9N6vBv#7yBy4+~)z*UN)o*@lLx4dk#X=lbsz!3- z&vD}TWg0SMG#O=qiM`{gVbseV8zGejyJjk*#PGs=XfZRQ?05#w z;l@;3)RVd-ld-?d;+G*ym~`Mn+Ns*JwJwR5o#!$-sO*jvn>AAuOyq9TGmjB4ECAn% z23G#`JpsWX?EYkv5S>jSc%#{#nZ^d9n zfBFHt<37RHpc6+Iy@v1TA)NT;02mlZXwWQ@McP8VEowXQTzT?c$Rq7KqB2F*dEn;T zLYhLh8gg8k{O&Dn)*z?P)e)I&!&T=*iiLMW!X$+A9>m8hSJAQ4b#lNa>YEgbnhqV;mAF@b!lAc6WmQ^KI_j}#*mN=twD4-$%E_(U+4J+A z&%gUXkb3CDx63lSoS=>lpGrouWiDlqcC_@9CnlXQ+q_@qoz6KrW%+!>Z5C9PRlQe_&hX-=B~bU zIsPh@Mop~x@a_%w7PW$pB8g|-!i&}bl~LbL*k8fp-9LgLdc@o6KA&@ zI9_hzOo@a+^;x)%awUC@C>JXSIW7mSd%3oC2v4I3zFR#Bf;FXS(zoFw}@_<)z#$!6&@4|DpxG?*Kk6VUb2 zqavRcWtcxr%%#@YU)r<^Zr|@ zC_>wT&O3qF*KYR2uD?Q!o*5LEadl?_ z`ZQzLIS`YYYC^jQu=K#A%K7hzCgH4sJ!_a29fQm4iS+#Z74)xVQhv3DW+4?Qq&!Q9 zg6|GvIjuW`26P}{?ozH#PRBsGU2zZ4(2{Y`q~?{}5_%7GX41I%oSeT|V&76+p6m-| zmSRZKVX5h8=Ie5%RKle~k@)>0NWWZnGZW?Eok;oA%LH>1vbTumsmN!2vBc=g`E6CrcPH zx*M5!CA9DA$c(k?n7#59mR%ctD~J2T?emX1vYf$P+o7wfqu1;K6iOI;@ar=edDda1 z;fRyFCZ7EVGD8$ug5DeC+=DIJTAJY2A&5S2y@uc^2-1U zkORe8DQkfs4qdswjyQ6B#fy}P5tWH`q)CNLRcK{aV(uhqj%Eo17m8+0<FMXm zir&a8=QiSWY6eZp`1=dOwQ4gd+D4pT^8=0sj&y$58@td*t{xZJ?c^tQ5~pG!{jE%5 zsgi~kFHUSO1<~ip`tl@x=2n!5ytg;E!N%K_peNd+k+099hs5if%Ob z+lI02!et(|{)}A3Y25*?`ckekDc4Uw&{SWKg{uWlL%VWfet=M=6r!H~+AAE|CWFaHDv>0{GbUI_M%b~mQU;jxdTBbwO)4st8B@wd zTu~(`&Rc0XwbW2c(%B2q20&{iq>xWV_4w&a<%9@|${F?ib42AfF>=ayYOmjY zJsD*lOi^~2^g{dt>VH}Pp#Md8>_oh*qZ%d+fHRI1t zW$`^+9iJ&7QkC@8x((6^eZ~7$Nc}gThH#N2qFQ=A<-yTIl1<%p+_g1HRS2d*JcVYO zwBD^xu>o<%(iXDqBcCVavy%``mJp6dcPY29m}MQ7wRQA+dOB7nM(o;GPS}kX*}V0^ z$|l$wc)SxQPthPDiR+L~G^AydTPuC4EA4bq>ZdJc;A}``rVKL0 z>Ew%1Sc;m}6dP$4Z=}?&O8Ts_5xrrZsS{^ptZyb?#Ti{m5&7Q%b#WLlx#o@a!QBec=Vdo*V&N#3NOUx9%+#Q!9g7Q^-ZR zU}2!|eO!k4FZVmizZYb!ZPro_PR5|$}bIP=ALMJD!I^%#eLK7fCK zEyb54Ru%ibCrB~6FPK44r%0y0_#&MqN6=-c5Lmj@-%pdHORvL?<8mH-QWeD_P2;91 z=ao&OZ;^{|3Hj1KO8D3;`=r@=Di?b-gmi1gqt;7^a5|46bBD9xvppE3>!BOafahN{ zMDjF_Va$6=NV#6Z;bT!e{m%ca2a{W3fLmuN_<1209~Ws1;SPoOrLQa$)o3M7$wF*G z!XOzHt=Hv3CWK?((~-Kp5>{#Jp>17>VMZCR{IHzhfItSm`aXr`#%SGqr`vDhPUWg@ z%EBVVQOM;2o!%JAn>&tRZQ)Coz7KIF^B*QZ-Un`<|Jc{Z32x_wML$=nP3%cubx`V6 zjdo)d#wlq$^!}^j`<&@A^HB-qD=1f#{#|g#x`}GC@Gog47vpZOlr}U|Q=Up?RW<2X zPEnOEjc=eU#35s$UdEgflX*DPbC!fZ$@NrbS7PCyCFF7juP1x(;?g&;*S5xQ>QM5N zAFP7W@HafjjhI|?(yGagIY(2q8c*pcI)=ffSR5ptq(m&p+*UkRI+dbEs1$D{p zOyyG2iWSS-4 z$jA)5oDBHt%{MVQT!KS8Ar@shi5h9s+FC4Aa(Lp?S$y7Y9(^J_gcquq;;8ea~PL_ne`@ zF9>S~7b;qGuqeEh!5wg?x|!;v(Y#06F3=HeW&A0VJ}vDebZVePd@Wu=Fa<(D8A|jZ zgVIY_KQohKUmh1i5+{%?$HmC+eu21$y7S4q%Lwe!9XBrzO7j$3e{j%9*9j3BlT7O2 z18DZ`K&-^9$#pV-rNHJr9LP!4z`TPL*iZ^+ql8Q1o$@6tQM{JyM#4g4#eE6C;3}1A zlFqbrC;PAvNFNUpvr4G(&_Una8a;0}ewsNS!xKkI(CDe0|BjGcxq8`>eII^EyU{(# z)Ttr%l8k0ckq8zV)aW(Q*tb15(h_O+;^X}K*kYo8S%Hn?I3c;4dE5<(5UCD7$^P%Z z!`w`a#Xp$zpH9fhg;;ZP$j;S3$5+_ITq>+6wCG!jl)4M&wvvm3e9aW5QEpi)kbU_V zB$UdRKF`)5QzUbz;bwGR@)8>+yvW~nY{xAnjua6#e?^pLN$7ZT3m4yc9W5cvA%kU* zc9p(85ien+XjnV319cfC2Zc4fTCh+^#X^*-qG%O6P)Ju9R29;LG9Iv0OWVrtN{Fl2 zkfj4n2hXDC@5!%QPmohkLt>s2y2Wzr9dO5TDeKhO3A4*-PR#w1uo*M?_N(2vNg0mi z$YA^vaQpm?$tk$nTI1edLTcqslDSmUMhMcro`#(X7`Hd(()Cz!C7euc3`Acd#=GH8 zb!&F;pu9quM4VNLqMA)@u+Ww9sI+mJjJJzI=1tNsHgcj8DNl?&4PCbBJ~ zob*i>s4ps@&m*JhFtj_JW=!BloHA1H0L=+iuT`WwPMBH6WlJ^J?`OBFPVW6*xahW^hiA#Bia*^z?toN=PK#3+L- z=`z+9hbA%J9I3bx0(6slH43{Zj}ItBqd@vFO)YIC{irO&g${6<9z1RA%++Pv3uI<^ha4}}XdNE>91V;{Bgk$2ZBLd)16W-nB zphCrw2(y%_DN$nCLUBCGH5kP!D7GP_ONo3rC$^$*jE}_qIyOcaHx=R8&K6xmL$WsS zXXG;zad$SQEORxrcNvIxEZ600L=5wW!`bMCxKW#Rg(;IqvhLz3hO`qe_$T1@`MU+V zw>qK1S}`8wZI|^1m@BsG#;l>?9{TxCb*nm=K+iV^Lso&b zxePe15=!zaFuwh^O{LB4B))7`8eT}hT*#(bMbgQ1A}4u?eH!EKD}7bofQhR!nTgeO zkLbw$!-+&VKiKz)YwXUM!m*DQv1{UVqE_u>>g@jLt7<4cn?sFw-kQuNsza>BNNUAv zDK(M8E1p|1CItraCyu|dpb`DLLW&AR@puWd-;j{uhPdd=5+-k$N0pfsXRC7A{qYJy zA0ENgrv5k6-vP>P+}+hr^NBF?!4JO0L(d3T2O}JmII~LRV{RgrDib^=xua)c&DIs$ zxpZbf<^$g+{T`QxRn}?Iej<(!O^whF(r3`z7dW$I9wXaFf{%(*5nU4#_)C@breK2$(vS-D6*3^`^aaQXj)W z2{G@0JC>`mku#gp8SrvHg2xOad;58guH8xer$5rmQONI4!0q!#pFf9Xx84|}hyf@H zrm$EPh&H9WPBZM)L4=I#%C+}a@%8CF^e~AbSN!eWaL2k;G;&PBO!L7)7S1vb6NOZ& zjHy%7lje^*C3P>$C+R{tT?Tf-e}Eq;(Z{(Ib&AwO zJMcD?@;^YR&TT{8_iuyIHK-tT)jQV)1-KI8lE;M+4B*nRy7S+9Rjw6eO@sVv-ly-7PM zA(M@w`^BJ?Rjw35QC=T)U7|u!G0L(5Y0A$6H_x7=#T^qumK|9_N*Y2&B7a-&>uX7I zN()&SyN&a2kHyEni7%G^_3k3o<*3x=U4j|D*UC825k6}8n59K9NrwXxsX$QCVmej`Al7BXa zfYAeSiVUZ*IRNwXllbK>ws@zyxp>>6k|?O9u!PJ^v3mmblyPZb zfTf)!#RW2Um9-pj74k2~L1A$7c4(Al5He^6jm9YrrI^n(w~2331cqJDq<6O2K5uIdrrcwAYoj0`K`)?c@GaJDWid%i|;2wR6`V z9J=0HoZ}7RzxtJu5Pu;nDTi{)L0i~eqnR$f`l8`##pRFp65Kl!eHSZqZ+y+pdsu5l zIY*-ij_+3Rqe~rzM{DUm#F#h!c7{4*G45UQ;3VW>FH)sQ?4e%dOPS(eB(ZYsOQK|DB4+o{ zP_kO`$QM$ssTRefDGi;KijlD;0b|1nmTaIlbLKpK<6Irn0M~O&(q%I$xTP;YxH^R6V}S4 zN=@;N%Jp~o*DAbPuffR|Wg>cud@4MsFaIf*5|z-SMd@exte2vXofpEwEwgYRXiELn z3~FmqNV-wN#Z#9cHVq?lDJ_Yp_s7^$V)*JDIk&WEqcoag6%8iz4tM0l!u9C+ zYI5?&Eu1@lLAeHePf(_6^mt{A7)ChH-uQ{Ld#}Rw&2U0gix^dnRW&(<)r2YvMj;pF z+Kv=Kxp?WOa=u9}UFFY4)SGyZH04gyCGi|edbMP(*p=FwN1eN}xa&fU8n1Bnn*$^p z=(dWWzak8ss?grLgOuzGbVkhb*#iJim+^whAYycxuS-`%Y}S9cQM;$zKGxO5nR?zK|}G%cR;nd zc<}X`BuF=qwo()H1auDoaU8Eek=<&@EAHhF%>S?4uq zKf000V@1)I#8LeH39?h-Ikfr&R}ZIRSs+fNM2qIK>c7|P*C*%WJ9HSAHE+lmXf4_u zamTt9-ioDL+gu#0@(6$;TW+T`epk?szrLC50F&bHuKQqs&tbR+MRc zDThuKv0>2%IJ%jUXJ$s(y=(!5E^RI5$@W%JQ<@+GozBeC0dvi=?v%rg=qWtPum=Z$j^6GQlr(WG>$$GBKL z(1wjT*ed4~qA-+!bxvZP&PuNeyDCpl-@ay`giMO6QLf3@3(+`8J1MbbS}txL4x}|o znpz>$#8M)b%){Bf8<7rcNZGWFrGG&Mv-^-rJ+z$M+`+5%Gi z#R$Zi{*ihqxKrJXT#PWZ|Lrz+(5OZwO`R>nKqRKvVkPEXoGC33FPkf(z)@`1NV!<5 zPajVstj7)E*s3!ajO@+JR&O$6eB}ePL5*hhNt|M8a1IGV*U^I9s4N+)QtFsYnyVVA zagx~9$AT0g2s4XX#te$2o4*VgzYQWpUl&i_*?0$9)hK{aO zI(yS^-Y_}dn5yhTD$bqdhwVGj6#;TLC`&FnpZ=b~k3B}_#YB92xsv=#CY9&nskyE} zwSxtw9c^$K8U|Kclx0iUvF9RJ3iDgV&AmXGKiEs>t5$NEed9;$pAALpcs6N2AH!+N zByw`3BTe*hc6TLoMkrs7jv#jFIIL`Cp!B#kkwxKJg(09` zcR^DvG9^t8ZWK~cI;*(}*C1(=0P&p8(y*O`q!LBZ#b%44H)HND5?@pTmZi)Y(V1my zHgReG8qTg*N{0Zy))^3Yf{ys9cCUQE^Vxf_3~?ZC_Hr3Gva_m!#z+UuVstsXC5D)@ z`{=4Cj&Z|j>JySVefa|BeKoPqPG(NZYPxUvfNHUoC4LcX|KMX%j>l6M;=&YdeH_on zksfn|?#e_{az2gcj0@&zI%=RpMD9)q;q4ks4ktw>6FH#bQWt1%y59P4&>uQaEXyoN5fm_~Yd3%kHQ| zE~RGEZnPUCI}c*gV_n3vm0_ej6k%e+#&6T9*qe^uv>xcXOInpCM%g70?9R~VCsC4| zP8U5(Zs_0ry-M0*qtR8qoQ*n$xvvvny}E)%3C>EVrpceOIIE=B$d?G;NeHLhib)+iYdU-U(YQ56$NF2BHmCl`?O%O1MCHy*S8?IApf^lizU%5CBGMLW^5 zmqu0|2MH7vTP2@WI;2O*z|^h7SVQa~Q5x665Itq`+h}+6T=cN;m$uC_;YwXOEyv?% z=+p_nG2;a(u!9@=qnvZ`JDrb?MI7gG+H1r#=?>ZE3&q(IXpGbIEXI%RGa7{->@9|R-!$~E4Y{*=dOny@% z-sTo4i?&_<<}@d+C$Q&mH1^XvW98?LmX;bz<>AaQ8*+=QD9zWPL!N{&CX(0nEH3Um zOUHH{ND`?Xdp!1L3GRS0IkiKeC$7@TAzdEgy=Ui2d9n$8E|mB!lKw^k6UOwVqNJLf z*vmMSmEbve6dR5#$MYUVUxRWTv!gX%q-HRD&v*C_@PzF*==Mb?jxF5}M`e&?%AgVZ zxpujc(AgbXp8cG7=`ao~+uM2v$x%4gDO^0a^d}zDv8l4NlReHI#j`2P-sR?#sgp&{ z{)5<>7&B_s8pbWnAwaVJE5gg>5~VTMaqQKTo` z=pN2TkBq^0(r~5>uqNv+^I-hVw@|ht8o#kUc`|AP8vbQue0oO6T!qUR2aFn3bPn^P zGQFCn?g5mnuDAn{21DL9lYZQv;N%J{?e zQgElbISe(wbDJwystYB|N|#8&PK?k?b~K6IDc1mV4RPyhL-D0blJ-Z@T(6DO!CcE`8t z#^jMiE#JwWkIyn_=$+%zVbfIVrk26%`)LmNy z7oxg;4gX#d=!bX1?996uS2wU>*WIsg%$%W8YjTKgaFDjOUj&((gJLs4kdqA1J26BqK9sfnJMk! zCPi?tC$f(lwR;zt|3AtrA+K%d0PDiQLr2D98-=a8HV^q>!7aT#Kp3ccLfP)1~iD zpT%XkBN_VosLBnPGq^L~?T$xBLe1hWzw-XUkraKq?Jn!V%up{Fyt{QhnC9X#9G)6T zV$@}fYbw#KHKxJ58k=_hq@7jPsS$6UCb6rv4faM2l&LjQRj8=BCjF&639ih5ogkrg zVwwD9VbINm8s$OPVhKS?b?H3Ki|BQi7&NXMd!x=^7v7a_{ms}ieKRe}a}NR5V(TJZ zk6yujL_1RTs{U}j6x^|HIW3fRmT>7e+T7dH2>pswPAQ8rDo#teCLAUqc)5^AnkHuL zjVY@)XX@l$tXa8%_5+7-@tYqp?K_BHX6>c#UB==4K*OWrJl)oQL(n^~6Zq&L^sOXL z)^p?9k)!mQF`AE`94ZBE#Ledu%dgr3KsiI$n`F^&Rek zgJV~YeYS!8-KCg6+LemWS1|A8NpL_q*jNZhQ8TAb!vGI36Jk-IEgr+lj{Gtq9H$C0 zC4FYx1)LuhGE$x!Nz>u<+EZMrzrva|KXG~12dHPA{rg1VzZ#saE%4|OLcjN3MRQ7T z%F8Q+)CFJWn!c*!JjI(lU^iZ=`$62$1Wj}Z9k0Q7LiGE9u#)QL`ygfVP!f3%Ez zFe44;$w4@V`$;?2ppH)AY(*)CH7)d?-G#ZAGg*0dI8Bo6oe&aWfm^RWOxgGeE%OIZ zmn5NiSrQIiy(z0rphm4vT2zdLDiS70SVN!?fRBu~MoTOsJg5=VHh)GGrt(NjFA06J zgeb0v^5|htO_4Z*BI!S6RD&Hf6~*M1LNG=eTep-XD&YnIYyG z1@u@niY(<;{b8xssT-UrDVBOQ(eIf^=EkP7{M6m+rQnWrvyzx6Q8Ljf`QL56pkHg7 z>p0c5PC;tdN$k@@DrqbAR+dmn5(hEF24hRvS5?M^IANeFV~lL8==jdx2p!&?-I;fv z@OnQ`?LLP3$D$}qukU2#l20XO79%LXA}YjC6iYpNKKf|381m%&hgtX8A;vt}i3%4R z)=&LNq+2^QRB9d?HyAsWHS;GtN67FoT#4O7n2#T4h20I^rLr0gJU&;_Z$C}v(BA0x z?#axm0rbDiYbqX_s8YMlm`-d_N3woe!@1oTq!q=3W(bKD=Zle>U|^=E!{goYcIw5} zuU66R)dh6dIKYL?Ki|_`Bt^lDoj#K@Bsu}j z6bMjVMh67 zA*CwWcRRh0km*@b?rx@VNz#iGYE7^5rN&G4my3d_mNW%bnEG3zQ>)2iFNE;RffDM| z%K32iT-M0X9(AwNbm$_h?xJAssc!nx?xjMA9szJd+TL4YuXrhIT|JsE`e<7QV(ndw zlbt^$Cc2cI6w=LYpxRf`3rThz6wb+Se&y?Jdnho};4V-)CuX=Go_UA_5w)h2?5`||7dljNT$mi->s59vzg-gNfw zy2AV!^TmrB61!sy!=9baHElgioZM*a|OOGdtWW!fzu_ z`n&erY0iJXoO4l$!pu6DWT^0-HMn(Rk-_9}th){4%CFZrw0RpIefw~7`?q|1J`_VC z^Lv4)O-?QTiXHuC!nD3H`XShHK|IqmAu}DKh8+o$+wdz+9PxcdZ!Pv?4tESn~a(2~<^RjRG+%c!FM-OmX?5re-K@bocQF0;nLa-C%XO(~%Em3w5)W+yOm7GQrKMt1Zp!N~kNV%~cU;FPTr_6xt@K`!zD&~B z<>IFAf>)Ic?CaUsmQ)ZnFocbtj;Arzn&k7B$vS$R*>Ati$%W5iWim;5a^kMIr@Gl% z-YnZ5Y4e^EK@Q3n7%LNK?*Yo~Y{v;hF?(bxZ#@4j;gdRI*rOj^ z4%gz~)r<9yzroW1L)h``CyY6~kKNJywT`^858icsgLFlf=kq~>2I?@SDa z0TNOS@gsiaS#k`k*^wz^5)+Tt3<;xVkHO7ZhY6$m;}j~!oKuK_gCPME!_cfUC(^GS zc2BvJuY}D@4l!fjQMMeBV~UcQ{Nft~yI9isxydx?sL{9;asC!cb1yUNi;p;yo5Ph; z@fdb-hG7w$d216zwY40r71g=*JRUFhrr(c`VW(@&!r2cI>?jIK!Wm^*g@`G=7&vVp zDQB)Q;Ds4@Pa8_o+CzN()#-aqZ>O%q+p9N$bNZ35uf>Tsw(!u(BmBDmAi6RxUSIYh z-X5J8`u6J-n^Ymg=B~J}x@8xVlv+doO%oUr9x9=G71I{HjK!Rxl725nzPebpccf2@2{ld$~8rB}u`e?f`z90Gzc7WIf)q+CR2U_fP<*rii)>xYyd1DxxPWC{ zCbK+tzohrXXZTRIy)6pBV*tNIU!dx=GC}0d`1V(|>bY)v2|T@rL+iGo6@QIT_fh0D z)RAz#f)k(bVC9cLFzL(hS@*;%wD)h%p}nz82tLVY8}Ci_eZ5kxN!O z2`|#Vb4Lz0Hj{JtGHe_B~|Ntc+nkGH0OK0BWayJ8sn(VLup z=L1Rq1!XQ)ocQ@?(jJ!eOVVgo98!uNONNgzFHqCYk~hIq0IE(^5oY9 z_wG%N7-qFr9VUsTn2+gAX?zT^Kb*zk$$@koID~`S4-h_iG-ZK?XvUQ@^wa5B=2wyw zm4cqFF}n-X$ycB30FOOCi`e$A6eU#i+VM9r zwlSgZPy*L8C1ePVVAJM<6dk?DQ>#89?|ke%r?0dQWlv)&@r$FRH2poW5&SkltV z8MyLQPVJ7te}o;j0bQvnHAE$%=x(^5x;=gTZOk3aNL_fAxG;U(rjKHK^l_Tj?2&Lk zgsQ_?6h~!3mjIjtg81y6CkP(h4%`0iT3`SCAfZyL)%5G9Qh&Mxl2d{)wu?h8A*M3( z-#{E!O}4*ymSPTPxH3%l9Qln#)M)EqA!b-3$Cg|$Lmk$G0Jc+^>niEfv6A!|O8WMa z{*t7(E9G#y6H`sjbD_zCZ7WLHc8{y+``D_~56ys{LnTBe7^A#6R1$fZ$vMhICC2GY zz&rg2*=CJ6x0JDEqZKEkinyA1?{VeAi7NG^Hz3FpqEhD3!{gJ|IVyUhupPu%KjGcT zGZm9ra^Ny++b5!0KAWo9Uvk=X2AijUP4shX(O7c?P3;0w)0-*Uy-PaNsr6yW)(dWt z&J39qM6lTiJ_;WN&pg9S^W`+<@t3@dbohkWL&iET|7|gOT4jWVcg9xBfURGAL&S^I z85r)vdn4 zlxJwEDPZraujw{-E+_UK#Md_jgLb{}*#8)txBYf|G~}t&BNAeWTQ;BOQXM?HxY60v zp3g)Hg?%xPH{HfDV$-YGTudYI;c;BKmO}4|!`Luq0Vd|QRGf=rVeKAPcbJaKVFNLhBl>HMKn7Yknlq+qraGgI!%M0Uwej!e|&?rOD@xSfEShR zgPF4J5f&`Fb3flt-Av8(DLh<-QM+Q@>U)QR@oZ|Cv zeQh3BlXH0S;b+OZI*w)IAKbG@tpwACKaPbDtJKxDa6xZ@c;OTt>ph20HoiwenZ(B$ zqS9`7;Pj{y4M)-_*crnsrQ4{?4q|)4UdA1r#gZ@odIU4?WgqotAE)8>&I><0?m2 zeu3Md?l`zubLBz~oqLB;9pX>MYhSbu@bGsYWvopAi+*?mjUCY<*?+q|7Od`|{_x3U zIxL!jv!ex<4#bfD%Vr|JT#oLJowVDsf~EdF=rC|F&9+jmE`7+5kYo3(XK4TM!#Icg zkx|@8Q&ACmW@Z$`#*(q+_osI>HfGnevvHgJF!ol~q{UpIN7GnPHTmtOr*Sc_`haTLB^EDF?r7S=5r zpvfK^4+AbmMWg3215GN4*BVTJgQ=u6%q2ScJY8Me6JQ?A>cmlmn_kDJ{y4=(5ty4q z(IUpLEa|A@t;?Z{k2CB^Z4T@^##o(rK1=sugyS`qzg2k0ws3J#sg*~-K6+2otn-Ul z+i%3S$cO@O8*(=uK|gp7I(Cixs4<uIilPpr ze}7+zN&>mCYBNTWJ-H@!Sb5?YMg|Yg2HObcKB259)Bn}EOc=VKucLls-;F3TUjLqR zIuhHS%>`v$8D+wqt{M32z@-9SDJY=erwFtYQ&_q3uSYPqF?g^_-BJuK$}=t+P+khA zvaMMu-;JSKs;DgSqH$}cYKv@FID<|V=*zLnd-;?n;+rL$)sS?pHI?tx@@XRblvM=t{3)=Ma^*LHm6S_eA)&NzoAM}*rJSK$ zww1^Jl;f4hM3nC)l818gYGsPO;BM&~W!pmPA=Pbt@0@asl2n=3B)d`OA{I&gZzMos z^6%^4KTT^#@~ZBg1Jv9>&Yk&TYvlQ&F|s;{W+%}0Ln96sp68t>mkV*yS^^G zG=Dtb?mB?}DK(wCoTdJJ0^j@v=fSk$|9^;0?5>`Os1(*l6L!EGv$2$5jc*)l^ zrZmEtYqWo)2cCU;W7Nr#byHR{_=R`)+qk88={~^MmfZSm!ykk~E|K%c5jeUNvy*<} z+yQZ0+kb(RC*?=!w=5xWZ%rPtapw7msaRw;x4t;6QBw=~*M>IyDX25cC418r3NDp$ z=KEhrPm5#DvYj+4PkbwqM^JJti*4tl_+G+1OKS_NYz#;_r@VLP!J&;{?h`uFg1^3P zO?-M2UwTg=eArm5-W-hU>vmAS-H{Ub?tv7lR#Aul9;hpwl&gmJ|f7(ZAq z*G4e+31vN*9-1p!*OPhr>bE$|4nZR-oAeFGaGWrK^pn!W?R8NpkJ9%GU}->m@^cF) zDyS#j?N%mjXv2L%Pn2}v3-7kBCu7smh0C*+fv$w-mE~ZfTrevW2~$JZ2sciDu%5IP zJGj2>2rkabtfvQuHiEfNC>ILf%^%yky)x)YPAon{fwG=VVLqM)dJ;d0JT0x_g^}Ud z4;;wz%Rl9(jvX+!wP}3`WEXFyp`CbR4^Uk8ul^cPzg`rT`|OpSotn4flya^G#IjjK+ULZ>AsHfZD%= z3~_c8s&E_RMA4R9;trjsva%BUg%f#wWPc*3PsPe!l+6P~8^PQsl+|~;bveTGzFuUc zXA>3uE1lo$!-*A%B2gv&wG;;G=0r|kz+sb-hgXV)ZNUZ4Q;qz{F2cR zBWg+9c8Q+{JW2BA5(u)zM7xpl*=%M9V)Z@*pYdahtnTs@%;OrX!q|N9M)F9 zUEbeYj(?Tc@5$qKdWA5Q&z^T#U+37>yC3ss;!bV#XX9LIpnA|pp>%MvL%XDcBZm%A zmz;^~h|UzGG*YXPkA9d9wO^hWvQg9Fg{fp`RdQ%?1%Xx%?#+&E3g&(xAyUU#GYAUr z!V|A8By3_hod<>7T&veZO9-a{;+6Ga@(KECE}xEl2}3gj0<&b_ecVM-2G#AqTf6=( zxb5x#`R|XE<6q@->!-g%A%)w2|BTyd6w|0@6c#Xr_d`Mb6IfxyM|Dx(&? zOJZ~?d$w%n>9?M4U1Tf$=abYnW?|LANyz0K5$}v*;jf1hl%&sY+v{WiR+HwKBX(E2U3K5RM>|%<7V51!n$H^tchaojqho!?hH|* zX{xx!%2)616K&t`M2BY?&X|+_Mp8}Ie#zyRbGFzy!TSBe>~hMKjwWqt#bT7Zr=<3)p>8@ z_PPED{<-eIb)I`E=X=Q?&~EF^{D!~qzw^4+dfo~Ds%-x%-`mIi*(8&8Ri$1PP|KPS zZ}M%O8P>~(Uw55?Q$j91fC;u} zMu~RpmsU~{wVve*Uc$9ce~FMp!6;!;vm1_&IMR45gM!^Lyj-%C$_#&YChTSG$>&(| zFJ2w{+LF=t+$kX)x3+%%vw?rHX4I{%o49?Pg4pvnfBVOMO8S3Jb2t3fxfT50y#5t3 zj@`^(A%r{Oc75&zN}m6m-|tEPkND@j{;?hIL|V#me>TA+)&{6YzW*qiRcG|AuFC53MzX#SG{RkgDm1CFl__W(|^mi|BBbWyXO%0WLz8K1L z&w++Cbxn&dI;tkw->kf08s)!6^k`8vOM_{kqi&#CLx-kjXsN8{C(kn8)H7pt+}B(` zn1QQz78RvtY}l|C&KBbA=7jCIj@VaNkyW6DTAfCic#ndQhO&L{|7&Qf&i?n@DO2sP z{XZUAfz43ATU$4==@U^_f)G#e8FTZu+xrwK>Hj&+-SE%p{}I18uiwJ$`t4Z!+e9<| z#Xsj<4;o5c6#VVmKc8TZ77tK=o%#yD^c+v<$4`;`#2zKSnQx9A3z?7cdIT2Hc)2VHoFmJ^9hLDPm zF4@0)4ek>1XlO%n0{-8uC*hmVne**8?El+x^qs4@wCOx64t>kPv{M{^`#TCUG;j&f zBjdywT3`BZVgWi@P*lKzi(5JK?qmXNYxweCETtIyZl8PZln&>P{7ZuAv$FTCt>4BQ zJ&xSkx|#l7()=U-G5tT|t*GCZ*WGZtp8tqH`yBrcDD`*{1XGo*Qrq?jW!T~;xPId@ zDcg@wp}c&qtW-Rx9cYzduV;XPy#e(p4fJ_=22G~+_~(4dqS3qC29|9>3t|86L4uC(`W5zOEIrcyUFh%ajaJ@H31O*a?2QdY83 zo_jWs|C*Fltz=(|vI>KI);B`G>EXQk^VC+`()04eqC5^g3q6*7v<{5*U|S+?!n)FUy)_h1d?zh1%2 zeOjenb$Mv(3$>Ib7dnmZKyrWuJ$?Fc^8F2Xd1`V}IncEcjTLc>-TXNXjdkc9KgovW z+wooaD2bnb4~7!!78cNPXXNqUglQvH>VFj??Y*c{&mE&lVp=l}1G>@U z^N%o)@cPKw?=T4&i04DSXf&?l>XQ9b*jQ0`Vhh-~^Vw@3u`M-~s_6Bc`*@S&)QoFb zFy>G6Vxm2gRI#NZHfOa}Gm;FpImpEQ(3Hm@>mbln5%xBq$tQ!OIn zwZ6Mh3AxO;xDuPeV<~>`AY0~sj{9ToT-Q-C=Hc#)>CzLwN4(fH?m4zidYR1pLKq)G zL>C9qv67LH%doFz;4<2Vg17c_`sY(PPH-YKM~%uT50#~iz#&153=F~~Cy(MPiHjc) z?uA_b5&urr(}tV)=ZSZH4Mpg_w1!77?xa>#%zFQ>tbX$YQjcCC-AbQ_G<2{z6-)Ao zJ@jhHhm(nts)f0O`;h#@Vrp{>n0IasQ}=D8!m5Q!&wL5Vl~7wwjj^X3Dkf20hNpQ5 z*EepX_}F1`_a4U4*y&~g9uWTP2}XIv?EBGUTkjTq8##xa%a+Ip?F^;)Xq)I$n3Tbm zEoa#B@~@OwTF`A|r`B{myGh5?HgcsfmWWYuykOU}-?8nb?Jz;UUoFDGQoM%nh`Q92 z@$JKL@bqTqp8c$Tel|g-jihC?Ci~N%5XXG4F-b-yzN9i`*`u)i!0eSdb*^K zXZQ~v;rL1do?F8%REqMaS13!QIo5w2IfhMcQl)Ba3MMmM;&b(Cr{$++Mgg#Q<7`ad0Ygi4<2OP*wM6j^k&P!)y%It$|rYt z(b&B}k=)9sa-sG2Di;Mx`ahah!R_-uh&vub(@e-T{t2z7A4z`;VCpqilep{)91mDpu1-kn`Z+LevbNKdSjt4S>{pk1Ih{*$|L z^;9BpyUr0Z%$@p}TCQc*i$lx=cP9eFdNFcPPb&2t7*^Mp0Ye{Lzx~%M809%c?;fXl z+1Q`Vlq}x;_#*-X{5ZJp0HjOvbQNN;kT>H^9sUDoTW4bh)%@ zD;F+_&J{(Jt*heP&+BoO&UBg5iy=?84muY8_N@oD|uhCAbSd;c>TufN(KLybDFZMndTZWB2Zl>_Z8F|F35 zsG^cn=dN)2zy(&lx}NUC+e4iO0q)-5tBsL~D`78+YR;-7?pOjFCVocTK5>3_mLwl4 zqM*3|%ypSM_em(L#86LE`-n$a_sJ({KVX;eUr#W~!v7A`@nUv9JxBTB;NFANpKl^} zcQKX^M^O6NQl6eW4))c7r7ju;IkX%*4THSE)cR&7T56mocBacTSL}Ucq(|qVzLWO;0V5*WnbGf72_6jo>j_3v6immk z?p&|E!g8mH8nGLqaU15xE8owfC8LP5IlnL%L%O@E(dyF; zw{A{!3hapLh?=SOj+i|jgv#EDD+{;Md+|CFV)JnIGvI~kpOAQb4P#EN$MgJRnp|%c z;y&T+s8%U|x4!<{F{)FdO8L9>wGDp&)p;M0lBdh5AHT;~-9oLVCYp}EBpnx080-ut z`l3QaA(KjjK|QsY)1Yk(mDSWo9i=?=D%lBn99*5ug{WwXtL-Q+&mcKf$i>TtE~5hp z^cRO%UqhgUFQHa8oI0Pulnw`P7T^J)O~J_D*)P6g+n{G?n%Dz|je*@Ubbs|x2(XY4 z$(GapzW6=VlV4x_lz~5f%QweQb7p`SVRK)=&n1|&&F9$i(MC>I#qp75I%TV`FmLJn zo3HrmY)WR19wy!TvH$DW7%L%IMSdcz%Q>8&CCmoL4;!Km%nC#I588Gfq-4+d=tMq0pp=x|XglX>UW z`LutiBL+SC()m~o&VGGa_tpb)R8yrixM$GQh=y!C0k&&E#|5wrqO4wE^Vba#~cq|+!PVRAB4D9$ooM4EZ z5#$zwo+&%AAaOiMH#&wdV%+i z19b~xBKlYOO@W8eMAP75=7 zhmB^CchIFW2!rCqWcVW$K{LzcUZTMsOt*HdgR`B_d@q|V8rM{09waMlf z$K1g3#Xh87xyqh3yYZMmnc1VK5&iWc#y>xUQ{E1gWVP_jx>qr>YC!u^2B{gMYB~q< z(~cA99!X%pi_cP#kWAj;%Q%c0#PEKDS+({}!Uhh)*gc5sT2Vz05dZbJsTIs0t5Q#X z9Xj~IiHuovby?QB(t?g2I5fgDo<%%a{v^wf#PdyP5~{DKQ$1r9=gc2PcjNawZS*=* z?Q(HWTSc1ZXq=mWrCA4j#p$HFNS=BZ@hE+oBPA6qYQD-_Ngc4uh~ew+{_hE<4Sx+Z z<*3wy;?I)w(_1tZYUAD0gAN$;=__v$zVc<>cO6at^$T!}&%}H9a1wKK={;@;>z;lA zBST%PE@d%g?^m39=5wt2N|7hQT_cGyNRZD_+^0I#eJRDbkzDq#y}Vee#0!qKZFZ}^#r6<^W3=0_rrtm6xx z?sOP9m?ldTy7V4EYIYX;W=yBUq`sILcvGrhN_lfR*40K7Wo41P|2UCfyhiSiTNwNP zM=XB&DLVLupcU!E<2u2tHpnI5(rbLTO1YQ%V9-V|ZD_+^0Je52b$&LiTnmH8BMpR&{St~srd=|XN_T})dU`OImA1&izzC3a7wz3VA{}zzYzNO zSE*ZcL0t*W#$YVNQ+d=)Reke!Gfg3&Ca^G-V`N`NGt_B8LlLwXg1&OBq*Y5=L!phl w3dzG3H1g#K*;lIpy5*o}0f&x0=nx(LKhAG=e#GY=AOHXW07*qoM6N<$g7jzJkpKVy literal 28105 zcmV)#K##wPP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>DZC*)4K~#8N?Y#wj z7T4DH{lDYx?(QKWA&}tiZUsuI0TrNymI`%us3I*bZE2xcDef9HNFXF3Bq8qZF88-4 zh37lp`A&hB_Ica$=K2wCo4sfD-fLa!S~Gk0sMKmT56@Dmr%!{1CTMdG;QUp423j9R zTMrsrpw2RYs{BYQ^>k^k+{~37rF{2;TE+hYJY7}l1@j>?5~|AJSluwZJu~T2nar^( zprZ@1?IW?KjkKzIyedwx?FUFH|KB;jPITg*4%Ms?s7ppY&fepB-2c$S#O&CDOPf6h zzF$q~#Qt2qRzjGkF|ij;6W%QXi|{B))R}~wTFZC4{(48-b0k1L>Zd;RGjX7~ixVMv zC9GP#2GeFWRc&pUdsw-@Ofm8Iqx`~oiUOSI8yZSUN-?(H7Ff1gQmb1_W_=wQKOe%@*N>9Q%S^B=Vg3G2 zHE=h0_+gbgKObzZKwA@Xi@@6(Qd58ZPD6UPv;-WS5Mu7^>whX#n|6AuSJ%Ht)rEbW z+i(D9e`|8Cwho$}t!l|OsK-1uoxJQSu?Pc}y;Z}a#eeOtrzM7M#$3O6O~}BOyo6kge5|QGa}~EBeLQU%ICQwvF#iZV z-Bs!*z8XpNl{N;2=&^cZF%3@B=vMOv0Yk_qDZdNUB0S0&s9!i`<)KYhIba!7x?O{%BwK#r7J9bh2bboXX z2F-dbc9=qV zZl)Z2cQwObozKDj8%b^OVTIQlJoZFqpa0XK(arxd))jSlgoTR3dvIjg&*axu(7gUE z!4pjI9~;V;`}#ps8!b&O)LuD3LUuOA#W!mB3sGbagUdJ5Wz-1TTMVf+(57hjN#cLn zLtAwQq5Z>%8t06ot0%OTpiybTrOTJ7D6PS=;zlK%_}v)bCA2!=K89`hme%qrHcWV! z0~fy{|N3=GrgUX;b1PNVIaFFya{TizNc~^~wsnQHG~cM86aOe^98PyZv#f>kl4P<^ zCDLT84GF~rPj%XLEzBEB>4$GK5bhL#%@bmAWc zjbH;e9Pb}NVE71nJv@o&FN~)5L!qP;7}DHQOYJpqpWKzggBPf3sOHfppQN#SFDgw^ zZdCCXLJj@_p}6&%K#vFd@aodf&?w6y?3szg?@NQqS{@rWP*h0@iPz8LV`;^GbLMj` z{x~fn4m$D25J@@L_MFAC&oBnBdj)k$Dfz!dbMosW*!Q->w6zikHw&s#%W+ybmXR_B zJ`YbJ(Dz0Ko%lyVwRw-9ddk==bRD#5DriH$hc^vzIT#1pq1jYSrJDyqzIr5VyvoF| zK{U-9$?m5=qyE?h^iBTS{BQJ3m0G1cgk5V6q7~^#Yi0$eUAtmbSVmb<5j_@-$EB{A zEyqhBJ&kUI`qSbcfcfQ*F|DZOr`?_T=^w!>8%L?Xd?TL9DivDQ+0<)`cRSQ$SWrgr z=pf1qnrO2yr{HQXu3dsiI=vGNx)b~E8M=<{?DKybG+djn(d(P#gud_!k?&8zDa;Iv z^g;5!N-IlA4$Db6P>f!43tC>*=$DnywU0k5k3V#yioXt~We^bA$mG2%@EmdvMt%CA zDy<+dAq&$eANm^@5S>^{!|_WPg}dVJ(v`$lKcVg3Z_w*B#Xk&dOCwx@{OR}70_-2V z7yp?~=%(l4KA}HnzrR4r^&+CeJ!#f&#wOgGUN61Gr2Fq-dCAlpHFV;i1P$fnTezD$ zURjx(ed;@`8f?Y!i>PVTf#_^ZYpWnOmHWSWm8032G?kSR`oNn!*>lZ}D*ifXT1bHL z@TRR4bWR+lcJmP`MXF+AO3}|Ty#D+K%Fibd`9xpF4!w_`w|vEbA3tYM^;ViqZ&cEW z-wlU`MACGe$=8mekI^7+TC`+5s_SaN3dX7H7Pb}^B`Hls=goPq9adig^J_v=b>ts%zNbv$}0 zL9ZEEzE;^diUwlD)l1P3JQ;d>Z@q1#F9GeDule=IXGJxx+i#WV#Bf(?GapAxQ zzKIRPQ2RzDo%r2ow^gIMmW+nl8oiQa;?A5PX>Wq40|$)rTfwk}_{u~mYGi)*fo$Hg z8vU3AuF?HQ4W0NWL8F>3nEQF-F{wX2?tOs32M5EoI7q7iPbD=F5~$I{&&HOMHMw*N zaiBsJfo6u*jVk^+I0-==EOn!GP(K!|d>fm9KGcU8;Bln}-#)#0XxxL0>gGvlLIR_n zTENxZ1ZIr7kM!yabn9S13-cLOZ*G^4NpaG}mKlZ2%FTaONt( zRvP$f8_*i&O7+J(arO3M)Ama&pK|d=6@ML8Z&Rxt4m?EQg(DpOX**44V+eH}EymeM za!L&+Ki|gc-8-1__6mNOv51}#y@@-S!bGn_e7>pk1l1pfov5gD?`$S&@F3bv4OzQ4 zy5qzv!On2Gl8VC_Vjo`kg!kZ$m+r-Ps3#7iqA+V|xKTqV{z*{%w98lh-1p(s9X*QW z__;=yC3FGyW#0|8hqQaaO6PJq}_0 zp^6@umc)Rq999{Wi%7Yw`xst1+sQC!CP-b%hVAwuUkkW+xswF`DE3CH)LU8(uve5} zskIitVJ^hRqzVyKNlNA=EYS#h+SpvqBlyFYI2)J7^A9YdAZ0SkC&qX7`#%gSQU2;@ zPyL8#q?)Fa`E1(uEknJmh$%6mDmx1*%FwQ;r|V;baIy`eMNfk^6L;>nUBJ`vBKFReN2S64{qt%(NdD&|>XC92M+* z^D_d*4I@Wq$c-vG@!y7q`ZjEx0@2-dl1!~yG%6}Ewbg@GGm&2n#QvPc@Mi`QG-)K+ z8%}e2>p>DfUC#(tFtG?s+!Hy=h1qhm0UMF zVf=P5QbRuj@NwX&kDtOi)SC_WJcKS*xO;Za%l&VKgLxQX@4tbuwIlr>nn<4+ec1QS z6X*qtEmT`V^mWWNH3;d^m$9oKVdn>5Q=hM9;-LH+HFV;i1dUqL?vz-y;}C91yNeqb z)?=EG3f?lJ1DHB~|dZ=hB&kj^8@*&w@sdet&Y#CUAD? z&sbWSF?PWun!M~q_8DT5nt@4OCZ~2DW80jyFf^Qd_iyI8q8ujbNV?F8|4M}FU7`4D zJn@@0VA0k_skJ+)J1#@LCfG_^FffV~Gd)6++gN;g6*fKm$S*9w#7xqKPW3dgV0X6Qn-l?Qzv84C8MG?Z7Nz7ofVef!Z+*7E4YefKE`zc}lXGH;}%mIE6u$g3G%q2}DP^Lqx4nZVbTlWx?|iGLC_nvB88)s6mz z`5e6W6PTj#D5!{Ykwg_ z&y?67e!#`hnO?I3aO&QN#N*=ly?v035p4+wNI)3u+KUhqSN6vr=1Je5Z`9C3X z%X0k9Pj%YFKMWc*6$8-i?@RC_Lm2+#Lb^XT5w;}@%V$8iDHxl{drbly>^V`DMR^Cs`X#QQMk}d9Z^Jz$Lo9Jc2j& z9L3h!kDi03a4EBM0@*(i8h%DcNjaZ~PF5YomoLy**^Eb!9Yl14eMtle4+Iz()7H(M ziVwEo;_1njZLzGF{MWB1(24&uP;L>}d;e2(n>s`W-;&SXkkl_$fbkxc0B zgJ)!q92VChg#UGzW%rW#3-Xn`AJHBsBbt(H5wc$u)k45C;my$FszKlZ^%?;9^8y;oeXZUCnG0x!^}a_ z038R&uBI-phT6P5222}8_wfU{Z`WFqVou(uq7(mZsCSKE(~=Lcm@tI#WBL$%G!BkP ziWun!i77;k^n#=L7z!cOLb%<=qm`82}Z0rB#pCba zL(Yjr)_f!q(9jaH%FumaFnyhDxNfOW#;O?lKR62Go)7Vk^+dGXtVzllK)B_rn6|>z zhQ6F`?!uD}Uvw--;3)QzPJc3UdZOuSKvD9iBz{-I`%CZadUB6&m3qlDFkm3)YQg8} zPtx1`0$r?5p*#uAu}ZQQ%eT;?iInsLJmSCR^`&s};_tg&J#MT@{mLuSAyVqP*t@SU zG_}hQYS5DXUQZHb93UU%_ryf-m*185Ep5`iRM}qqLz+*rY}ZA$Q;H^~fN*C=c}=ND z{-L1(Cr-hJjepMky!GOf z=(U>C>flUJ_ikjzpOY=y@$wHKWYN>~%Z=pTLB)4$jGx8LQZIVToWVl}lcAT5@mHIP z_3MgHN-lV5lCowac=~|0c<{ArguT0h*zK<|`k{A7KD`ICvKG$ATxICHACsz)jny}s zIUm>+|I`Aq^D?MQmg6%pls(BI)k}ZA^BqryJiy~4|DF!hkf&0ccJpD(ve~5Nr;xMv zB;~q#P$i73YYS~<*y`wG>|sXrl}ZLaJBKDs6C%}Hd3Nl!P7?TsK_yX4J?+ca(Q3;f z<-j3yOianQGLrCm0mu77d3stuqDvaMbm%&5^*ZG4+71tmXL3(}nyNI(x{!jYl_nbP z?IiCxgMo)96{C8gyG)eCoW;C1tT)k3S1GO1K$V)r_DA2L&(A-xxMezH@42-T%%7)H zSGqcKqzWeoaq>$QKQ*+*&{V=;e|BR+?5%leD%l8+~65!A&SY#wiYcNEz-2L$U2^h_Vo4O1Z~ggtF|cS>7q_s;!lM%V&w5 z*jUSZ>4TkO7HMg}Z~sp`p;G5c?6H?R#9YWO5Y_1~s!|l@&F_i|!P-ugrF3#}30TPY zN|P0pnJ4?Vmd`r!zD%UDu@%(RKt;u`+b9Rqv8^_2+wtqMsV)^osmFgkkWiaCj&8Vs zseT1DSIek0lC7*A;EZV9`U)&&_CqV?I>kyWt98N83bSrD%#BBPQ(xc3u$_Zaai)S93;>tl*0~Fo5A>RO%sP z1Nlk!6*8m0rtF)8H+~TWn{&@SFOjIdmxSjIiRBvNHe)K8-wMH5SmHis7#ZKL=jpwl zaAfB-3Sy2@xL4%zO~^LWr_W?(aw3Mh`4mML;66D-${b_nv(8*j&G}nCKK*n_pnB3^ zBW#2HsBA97)whYlY)ILE8Vg5%%408rxgoY=ds4J3x?@Rtoyh}n^9yJ8xFF7@=}>*< zV;-Ej9J$;FBcpsq8O5O7l2-KN2`)swAr9NZ_1y+d8J(1m`G9g7sRWQNZV7+%1_);<$lN9dAHxUpN~rY z;AfMFPH1IVs6H#cOGCqFCO*Y)6ER^5XWm*)o`yYXJ?3M2^bj6?DsugYVBFHcP=9lF zfAThNq0eK}Et)QN27K^o8df7_igJD%N6*2W6FaTLiH4A&RK&&ODCz9Uf^ z)}8ajqbl`gy>}=)wwc~Tz0kJOBmY`D?f!1m94kP5F&3dDFqPwLc4EM1j}qY7jbSG1 zIKF&2AH93~&VO#CO8r5-Nd4MM2}i})jtQl$){)VpeE9C$Z*Z3cKSOvBwNU=W81CQw z99M<<_1B&tdh1!RQm9sl%~aG=aRDw}EXZ~mfc4cRGy*g*C@UueFR4JtA*P64i>Kh~ zVuzhL*MXDkiT~y9jq}e&ZLLaeY%C#1;(5T(zGw(ZCcLyoJ~m=(XNccie_S2AaD7KQ ziF#V-G#At68q6#kds6H4IQsH);Ny=`Sp}0;y-TA-GbKk8IP%#K_&hd+!4J=+@a%cC zw5)0MFeK&hQF;d+CjE_A_TNGg{Cr%ce(y9Sfz4$!Z2ad2`QcVNzUidvS z30FxGW8Qy-~THg-ZR=3S;tiusvu_N5$gP{DL-{u!o5997A<1+ft6U-+K{w!Cp6V{l<)b}!&DxQ;rj7Y z6dN?qWmIokjGSrNy&J1wDQi&dTef?l-alx4vDlH?<_Y^&*!}iHGVpk0K*#xjgdNwY+HHD^Bk8QeuVoIZAoaXCF{}!`t={f@zon({1AdI^znYE7ruGA z6j|wTxg?+1X>W-()>oDyf&d5JUGXJXYYQn)zs$*)L`+i)FsRc;zpo)t)-f!9{kQw0 zF;&KD&l-jda$x6@=P2G4CuHL&5+x724_{+N-#(mhEN0dGPwDDx&*{&9!TpgZ`F#4P zjOusy{d{x(?(Rh#AGbu5p|P~92FrfE7(Z?XnJw2Ky@6xJ@wg;pi9hP#G{6CSjk9c9 zbL;(I{6(c67_*Qb-GhmLV>yL!vC?1SOINEferFPk3zW(T&)zeU?Nz^ERiP)wL8X3ivRXec=9?A!g zZ6?U{aMQ=&dL?e-Lb!f5ndXXiMm;l#wh|*u+?~nT zk;ste`@-k5d3*Kk2mZP7q6DsWlRzzA%fRYOn{qU9>}8LK zIR1&B&+*1{QM{5R)hqH}Q31;G>{aN)r9~aTSWjj@cRg75k0*TjR$v$^o`lTF;5ss9nR$%PwfckPe z?h}S$QD4dN-7%tu%5a=9f|BH1n$xTB@9K$8KYuLiTd1n8r7$g-yo6lrhJ;f1?E&l_ zdjKyDYmD@w>0S}T>nne|Kh~u`tX}Ye4(c60QCU#Sh%K*>eJP8e&|o$`^eoOZyNPk9 z;@B^gs$=JId2lg?TJ^l=@-74KyZe5=xqsEw3-}o5k~+YRC`(^bB>)vjI#8XRi=mS% zb%)MkH+h=GLgi#ZJU*34e7*D5`+s5IAoZpVCRA?QOIdXjJwKgG&SmM#Kr_xhy8)9C zAyi+;LdVw*?c5yvMns@dBTgNiDE3u`$K$h56{eE5^_0}6qUYyxNj?@&kFmWu{Pc2k z{amR|xsI`0m=M)POhSiXuBpk;pq;3;6+fkZ?Hd~c3@RuS=eAO{P>^_@lP9jwoGlh_ zlnW;IV&r9_1S}v`UcDtM94f`AzdJVK_|0~0)I^JMg!|BLW<=$QY@8#Esk3MzV_6Q9 zexAqABbPD#;CpmYb!A&)D$hADWI|BoEjwo3BccSN_K=lxoPf~*_*OTw>c{U02o7P- zhnt0rwD9cWg{GSq#TQQT$}7uh_H^azHFFr+be!dPa86*Mqyzh^M=;^zci9vFA-)FP zIrh@a_)Zwf$*&KBfq}^2HVOUG7ZSBl+eri{l+WVenJI{(5_RN(n{PXr3L$F9cA4_K zxAa-F?7l!pB%uvgos%gOZVU;Rbj5o(AFNtUx9%yN|M~An0@+o5YS%8xNltIN%(#yd zooIIST_;i33YS1_)YV$pS(q_6qNTNU8L6B@yn^8?>m>o;!zeB2+)4<%d?Ax@GZ$A3%{>W?aS3CKsJB}-C z=KUle{gz`Zg?L;H3AC|BrKi&oi{i>F@EJ6PCq|DY)69msxZ>@2_x*fx|1TshlN9DE z$7j7piKx=h$-Rhw;YS<>_)}S{MUA5#FO8lrz4*_e4)&~9u@by{i1 z`LL2YW9kzwO4%T>*BTsMwS_Pm&_GRAg&I4bKn6zkps2-?;hKw3#Z?YahnOA3+|`fgl@*{o>#t4H0~2L-p%UUW#5r0p zaQC1-Ka-H5!KCJ9Q*Y@(K!6Tc*CjLDuRE&6li2&z=hU5!#lY~^9iyZJ1+G)r{_j_k1|L{Prok_&Fx)O~>6I!hrunhDeTjV^ZA~}^)(pJ{k z8#YtkrlKjcNeEhqGgZD1^8qihlPxl49_9>wb_CbAB%$l2M|FIyC{S<8_HO6?W$su; zC$aPv>TN~59M|YykE=W#(oL?v0u$aOm)DI@uYC0qRW%xD*A&oT>@R&!Ni5k~-pvOoLVzwkOY@8kY zMVVo2icxhHwRx4|^s*bZ_-vRvREp2$RAc8KNNasDZ5GzFmgi#8)rqW4m!)rP`1Fxn zG<*t=GE$5&VCjbp{PZP!`iT@)8PJwe#P!o^%!K`^6{oL|JXf=pa^)I~GBK>g8+JVcF{!U3v`+w^AACS*`z_Hb zyfUm`J@?1N;xd04{XTsj{cAZ?U9G2093M)k%GIIx+ap-s--qEt!$?~AIVm%;Fi=iI z-vKnV#8z9?JYP`GfML!|pS+k;i?>KFR)NdxL11PngDst!k%eZFE@vwwH7F4|J2;Z8 zD~-QaqD;1rl2^SPFgGD@t1x(V5gW=RXH=fgr&5u3I+t=8&`AFsp7rm-Gef9Nt;C@C8qR|PsS=W_QyY?f@`{k8bh4>9yGT^L)U>r)VIAoy z@Aa^V5IgGcEwzbq+?xhH#g6QZIP~lR%x#U(wRFJN&4Rv<4CIT8XE|QA1ViQ8)h!Tc zCC4>Uj_b#F30k(0zGFhsk%%1fWMB4wag3Z(<&2%so1DUOy7qEp?z;8NU-dl8uT8j7 z!`L9(!H8a6&{Z|kfBsNPB;`Bw)me-@8!^&w#K~O~&mqH@E6OHG?^W{ez!q&S zO>hefV&Ln~;4p6nLH9X}IpyL!x<7emB@J$t2y7$r%&?G{b2%7_Ywb^x;9Dz>u4_fD zd^YRn2Eh_^x(ET33n8S-Z+-@nf?c4rwUIs%mNZq?pkeIAfTtg1NWY=%Zj!X3Nzz$I zS2u=MmT+X&>olsQw)lL6qb2%h(|Y>6$XhY;0NGGTU%8hY35?41MLX;tOLXxyqxQHs+Bua(fr}mLU__q*y!XZm?mN1X1@=p*S1wH7353kU!Uxh& z8_S2*Q}NuBDiKQ>=HyKFdm@3|b#eERw&-b+c}1eWrZ8ZnGlj>`;;uNqatnn-KQx^s zUltNb%fxYFPaMKU9bAxYRZ`v{2PfI)*~gwDH)a#hpWlSj>AAEjKJqt&YwZ@&wT-y2 z_In%+9O-edH+G>>Tsoeua%CLXh>WE#W5Gi{o)jU=2n!8l(jdv z!N%K_pohDnQKZjH_ek_~Y2{K;sc@?Sb!9Egw+(0e#Vg!v{Rst1V7Ucc_2sxK<+y(Q zp4O%&EL<&c8r6%FtM{)(bFHigba-)X3!_EC`>g!Qv zu1V;)AmXzdXpXNF4=f=&QG*tf8VSG#%<%2OwJcErEvqG}->Bdg7}N7S$wvNECmcoF z)*hb_FC2#S8{8ifl>Nu4Br%SuDMSON5U|I#4V9xqhQ`)F6ps)zbbgXCxt( z&@R!x|DzrpJ1o^Srr@rvNrplK&BC~vlHjXuyvjI9Sd+ex_dfD^QeL|YDdY+%c=VCu z7N)Um#Im80!H>`>P1Q{tR2T-MvixvvD>fTs`6y7lIvZ(U!<@^3-XXMSi=E z>`rrL9t90Drn=Hkm!y5#Vg}BJ)Z~aGD1o6!jKWe>p{CeKn?wwyb+s~PH7)3k?m?pj z3}byW`6vM+Azh?y80xYL7Q8=`vO}-3>K5k&ZgpIxSuND5iZO_=rv6h&J+#^gd*MN{ zQc8(lUP*vO^j-J!&HaZsYEqTn$mJu^7&bLvb>DEZ&L(4QtbwH7kR{{q?unV$(%1*5 z(jNN}KiuY=z^&VsM_r2qVKa$}hpuAoXhf5pGn$f~dG!w=HTEdAxgu$mnM8_~T9xqb^?GpA7)^yG`vyD%N?hqaaLBu8>GcS%8% zsbWQ9U?Pe7uYQmtKOMwBz?QPhk~@iw-4Wz4`Yf4CQ1>WiJ@X9RXGGF-l#oo8wBJvY zW1pwM^%JrmeKM5DFJ0rNDf^YT#M~eN@pp=(f0R_IO}>+E>!+L`)DU89kjT^|a`;Rk zBNvWg<0pGD$kIbMpc&7{dp{Oq_2^a$iCZ04U904}A&x=-7wP`$C|=un6l)7#dJejWgq(kwz;ze6x&Px| zoglc27Z!tEsWY)BYxN;%R~_0dlEbHGa?d+2NPKdp=OYhD+FDJOqL^=kTeeNqhlRi7 z{z3r8y!sZd4Oph<^YF*>__X&T z21a^_@76NYVH>&M|g^NlHXGynQhPm^@m=Cz;iJ_?5 z+wsoXK)rCp-w_Je?Yw6mA(IC~s4btq{*AD)kfO4lKz=rvJ4NOByI|p_kgynlaw1B) ztXw{Jq>Bk+2NDSI6I-#eA@1vo)F>yCmHEWw)s$bXr76;%Q@i%yI=~rgZwK0ynWW0) z)mz|}$CW7c9+UTxez*mP@ZswZ5I8mvyJ2BMMv8&nJ#Owl?4?KfA$lL>8--k)#kTcC z4&`f8o~fd#Uogpg&(iD{gtdbU)$KZ1NJe}++^TJ+I+?WXmA(sfL|gGcW#XEpoumoP zluJCsOGuztNFrNN;o_8D!pu229RK2kkcJR-o@^H@zxxH^9_r4AZ!IUVXCK_WJg6u% zLL*BjMC3pk8AlGHIVg-c$)VC3#mS^n^S%z`XJ}v^CY2gWB{oWGAS_=b5?o<_`5H-6 zjFm7T9Du9ruvI$T(w)2`;^YH8NX{*%-a`j{b8Gax-T3j5#TcGEMv_K9DR65@tz5lg z$^Q4=qsxST^{bf9?PC(x(&I@Nf$(=kMXsbtr?ztG z%~#M8VjDR^9I2O#?a4$*nM4cPi5;jbDL*8h?bVKj;`kP#_!MQR*nvWfis4tw2#U>O zsg}N#-<5Puu^~$bS`VE=&)<_@ww)xuxSr%fCv?kX+gsq4$EB=uU?=P=yE(b=3&Q6< z!Z%;;!A*|gc)mEypMabDZ%IqX)z%vKA(B2SR|w3dkv2kN_DwYJO2W9SF_%-~D3H`C zqa_f1$&PM^TeYpt!Go%5@pcI)<%;?=wZTGH{88!SO7XW#Lb$CmE;h2Gayg!M*+$a< z??_i_PKq76$gwF)1E?xEwQM`X9~#c4!&}hTH78rk9*w5lIGls}FkGS`e}evIT>7FO7Wc`|I|AncT@ z5lXl%FsC&siKL@t6vmuk_L;AV@Q7mfjt%JFfdo^~A_S}bxD7)dUq<-wG4e$^&I=}s zauLG`vZUuETO3-&!1ATxirnoj?P?JQP^J(cw&7+e+e8Q>gv)U{ zIN&U*=+x#CVxIbd>ARLQ;rWT|w|l$e29&=e8eF@w`NgG}jqcCb;gK9YcnOY6)Qb#& z!>xGtk&Q|evKQ~KR8y|xZ6y*Mlyd?Kb1SwX#7Mb(I4`!MZ;X%Rp*l837`K+<*~J!J zLql@69AMnz({Oh-r7~v?4YzTYTOQYyYebIrh9i0Cg}Bj>o50K&<5+*`G$Xr6r1}$Z zbN{`A+&cnANrM=V@>HfK1I!g$_3_2X&5+AKo+l(c5_fkuYGT*Yd*y2NjRQxLiF8gtV^97}j=%pByQj?|di5@5%^!lks-B8-`P54UtIuhrF2q^} zvO(Bi*_-S@iGa$0D=?5h37{>-E$BCvP+Ba?!AltOy2#_}66$iwnXz#ZwPseFt1V#9 z2P+A^cPv+1hx}Up7Emss?Wuc=4~5|lz55LwdPcZ97~!DgJ+&g03X-u@ncy+q9X$(c zwyoU3<+BGcANCGecevc0a-YBU!|}XlYJ_%>KEoG2&Dmv(7}qsQa$XguB~?1TMMAo# zbhxKjw&MJH!hWIvJ5+!I#o(nsdPLb0)w?%seS|IAg(#H82@1}2oFH_FbY$WykC3|a zDp^1L$bgZdD1z(nh>Ch+x{T;b=9&}a8W>`n*MNhwa;a2UuC|2KePV3pLU4{EKRZrH ziCrlDp(q0Rhy)$WYH7Plf`F|UWl1z^8fyBhy;g)TyZsEA3ROTbZp`FDK zlg06Cjj2(TmLhT#UliqQsVM}JFG@>C2%%KIlkSf{Ep4wXBK2YwJ%@G2f2benF(OmikK-Zv}(0Rj1TX}?{tjpA;w_s>*M_s-+fTeh2os6)tc)XGZ zG|SEvBFGn(R}5KC4%SlMYm4D)=VI7uOhnIF1-D>ZOkN z{N7?rSdJnVe-m@&sMK8t2QY6(C=ZSZpxV@c<8OaVsq$ppVlhDF`gDaHP<;-1&Te@7 zc@y=(Y<^n1iixVje7FAgXWr+|RjG%*yqGCNcJj^HjqFJ|O71J)5u>bNayl0`UvDx` zNqS(TXj~a|WhE2E>6OPbq(}xNYCyTAHB4iH1rli<)S5 z^m%a_4naey@$81zw_W&j#c%gVeV!bbM8+{oALe>?61n?M-Z(Bf4qdSYbvd@$dJ>II zsZFY3&@*#s(l()6(?*^jzvHg^`R4u?YBzlCom)UFAMwT!o;ka8Ov*ybBr?*@O(AON((mw)&?~?K_3~oed9DS<*~O%x=sK zj44!3jGap)Xyi!7jx$0}N5zL9MT_hg7D|;4?NS|o6P zdt=%{_u&DY(-C=DF7%y}M$h|PQ8%}7=AHtTRDysMR?fvpMyO0C>k}GEZhIj` zLY(z=q8v1(qjNJbGPWdOQUt-08kQCslJ(0OM)a|xJo5uqz4P1s5ue;sJ!kt=tc=UB z?h-;{LNpJDc`@qqH8hut&F(&h!~H|)(bpaG9s~L5{@0kj`51$YKJB=(P?;E}De+4= zXDk0Y1Wd;{A^D(8cx{(gg{$=CKjqYxBD33-ahBI6IrK@3LU?iOJluzw(sVVOhWd0; zua|S_^ks<4#K_!AoWEN~yUI(9$lQ9C;nJ?kjQ3da_N~YHmTfDQw(UK2qt8u6c}OQK zIfgQG_hz3fC&ER52Ut0yY28kZrUjYTq~pbRmD#nNNy2$-Fu_s5lD7M>@trkHUU)y# z2K<6XnL3EPa#O7B^=YxQ$3VHpTU9NLVnDfxirrUB*tjtf4I`nN3>k0}iTdYc=W+~f zsV!t%YGdgyQLjOMHnNz#H?N~iouH5`z8~es_5!l zL%CD&iuX{eGZL^GGX!Hx$tmk30oRmPw@Aa3k!v<#K!hVFU)+G6uO_E{*vk0}7nSpd zcLe29c3;WS113c9#PuIJx9=+K*a9ad`pdxATh)R zJ4w&GNa2Q53UZV9c%2Zyg#;{%#Jd$JlZ3RzPAZgZu|fdKNm1d*|7n!);EB_jWSlQY zSI0`sq7=(k>9|rI+N$d*xl+P63zxF}k!SE5GnOmb>1Zg??G~srmq@r_vt(EW9ACMP zyzE34tk_NSq3d#7zvfo?+fHyYI*m2G@1r3ugH^B0;n|fMn9kZt^c6c29M;n^_5u87 zzsxV0k!kZ=jo>H5WuJpesAq z7ip`U{Z`;DM`0-}AD@G%n=3v8EzoRNVG|h4!RLNLOPNSgCUjaed`Smlx~}BHS6|V8v_BfnR^+!`5YjS4t2mM7lcm(DM25D?F*Rs2WcgzB zoP3F@*+ks-U2M1mCGfbU136WXuay;3 z@1gX(FraeR;LRLXNe>itpqwMH7cy{=ep2$L%mUmz9LQ{uG7Um{$rVH{TZFTHZ=xL5 zlD>I6pZ^V&z@Eb@^{By0XlcNty9e(5dl2`%M52aljGP@Xv@@oxSw-u0X|TKW&3S3O zuWT+#wd3-!a3DG|F70Blp3)!2V#LZ6VI%+$`k54O#$(Dz#=rRr*N+@0;it9aYuYmWtwpq(wi5ToCfWl1$vw28 zBMtog^$3;Gt?^qKV_2V(bioQv>iC??%sj6ee3A8D6@TeZ!|#Yj@~ z-|lk!@26`G{J$WR@SD(dT+yI`l zdQHT%`V>yF^*DzFq3dWtL3FP8lhhoWLtAYN^-hv&`B;!HPHbk;z{KHE^!68{R?dJA z(iI~Y$sZ?2Ai1BhRWsfWrd&;{V$Tz6F?4jL#@U;}i$=@##?`$x9mi?rbn^41vrY7Hc6TLoZYW<&h$Qaw$ynKn zG55W3pPGdD3Zw=3br??c$H~{1J<-Wr&d8(71UJU@7|!$udrE|;#7LRMY+}QvZ?dS~ zmxbT`ebIH7vMNoC^2#CDol#Fsr#vl-o_dyC*T4O93$(?KVrqRk7kwOaUnjnNVI?if zGZ30oc#RM2xH3_dl#R8GEe?ajF*ozUu00C}hj~;>U)%|dq;Fm;n%MFF3$Ob)wd_1a z=91Es7U8KZU^hs9Xmle-l0;KeJ?4LKijeM0DHX{-^SM5Rg@|J)^9K~1la9}nKV`8w zDX&o^8K;wwLX{QMyY;}q)sxLX|H7 wo%Ig7m-X1AC&Yr;=86k%P0BkpJ^udcHjc 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 diff --git a/assets/bigbug/menu/bb_menu3.png b/assets/bigbug/menu/bb_menu3.png index d7c0e8d0b207625fc317fc6e49e78370d6557311..0deaa7afe348688d44d0ee9c3640c2db0e841daa 100644 GIT binary patch literal 2375 zcmV-N3Apx&P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D2;50TK~#8N?cEEO zqB<0X;j*WsW709{m~>1wJ8-NRLLNM%g#WwC>jRNkYi0LIhzbbvkj*IkctyESQ|km-cs@@O8=1;9Q{s$fosliT@xzWyAK zNB5Y7p>n<+sbGK|8Vnc<58wTHe+vqGj)lvy84JY_Fu*>&0>%#%RV1O??c%O2WMTmn z+fN7>V4n`hpJP!KB9F(-4Sy6e3K-kRhD2e&_`@b9dV&3Mle`uLLluZo_~^M-z1+IM ziSfflny2+8#fp*wc#SCOMjXrtX$et+PZH3=dGauusw(K@!U=FwnLxNg>k7N z&-HqD*Y+HLZYP>se7peovozZJ{#TX!$+ayj+FMK5VD>sA2! zsAPaYN5P`0jFNDX*omB11tT`m``W>)W?Vuw4__|Bm5VP|wdkdLk5kD2e}XMT$({VM ztnm4{;Q~ev*b~E$@!5{K3P#})*}V)Q1FWhvTr`jBWr-6`vABhHk7kV01Wor)OCjMu zS)pPFOwGTD=3rt0qoI+T+|40mfK}91F1oUq2*X9(I+!qAL{Dy4?%Jila}DjD3zyUB z)ZGH`X=8>(sc@0vuWQhY=7vToUYmLjrvu>vtgM~dC=VBtDn4Am)Kn)588yT%F}Wd> zQLyL&U|l5(Uu(lfiYpkO0wh(<3n}f!Li;M~w9ZaQE?U1RY zVyP&m08FHCsT~&SZt1AU1&m(6-ywHEv2ZC#wp6fCy#VWICw24T;&<^jT)I02{@DR6 zDC~Z!I?1J18(3{aaurLcG#LSmr*O$lWmqOym$Y!H>7)N_(pJIJUfzQy#-1}3)eG=> z8Wxp?i_u-JW2-FyTm|FrWVgc##vV@vPpGyr0G3rU=MvNV{9qwh!H6Fy@3$>j#!c)F zp?U#6M?<30a0wU0PAL@LKI|A%n|3;dZC}@*hFxy)1$421v9ip{x8~3+k^v?vU7;(n z>5s_)GgO%jH7M4IkToh6FhkXgUN;;Y5;DLvB@?sM%IJP2Y=8=-!=fd^MWRC_zd<^1~AtByMC>)xE`2ad z>8w9w_b`Bl`bAB+_`2BjxaFJxd$UGs_evmR2E`9*%&8FV) zP`DUE27B^|{0`BOPY#QGuhZ0R2=p6a5}UH@`*!Uk1R=fA-0*BDT&&%>KbBi4$gdS( zBQ&Ag-OSQFXxKeI5iXLgV2s)myVTGxKI-C|8ZMIiW3>U)-2rwc{ISGOg^Lsi%#bl{ z^@B#8Kb0@JaLLJq7qAD`k3JbL@-{?juj@NxY8Snquxbg}54DBweM-25@1xm;;Sn~Y zW{9+=$ra2T6hOIPwF%c+QYg)*}#*3+Nv0!O$yDNuq0oL^vFuxT*{e7ZWi zE~VjO{7U9GH2UFU+kEzr4oA2E>w5)E__YdSLMBujE#mX^qvwPd-l`nwrvHB!0 zfbAk+04$~#FsXGhW48g=r?-HykcfrF*lhsz>n&h@&+d5Vk2_E^N90PNRWz@$z$kKG1fzj{NYR7i~7 z7DKTEY~QMryo3xe&R?C5Xupo-_p)lh$kIPs3*d9qpZ&|~0Rv#WRu7o-`P$tAur*XL z02V{Q09XtG17I-(41mR~`m-gbd5E7a0VG&!8-5OofB`mUYQRiktM7Gq%Nh%;Fnm@WBCYiI0WiVsc8T`gFkEES zfQhsRz!$h&F0TK!RZQEFRl$T8hDg8{T62h0gR@8g7DK=QSPTIJU@-&?fW;6n02Z@K tz-ab6cFr8Ywyajce8VX=08{+@{0B>dZwA^w+u;BJ002ovPDHLkV1g92YW)BJ literal 2249 zcmV;)2sZbLP)Px-gGod|RCt{2-D{4cI24BALn%8tS(8~4tO>7){y`gzF*Xp zRTe7Ri-;K0h^mf14}+y{pHwiw4_d8YTzL4b*l18#+lsX!s^-jl1MuO-fbj#xm}c?8 zwyi})dlxeP=Uv6(6)^xH4;K{f6=$sqF{Tk~TM;W$$o&7EwYC;_yleWNhF;+J{E19K z5UX5#szy|GtX^8%X2Zql8oIE7U-QUSE}`LbL$IhU--%Rpd{y$Zr)}$__X^;{{G|?t zRY=bC$`lV5qoy}%`mX8w#&lFPz?WMMn7!d5BJ!-4%`9Q%GIVK+7^95*1-{&BH+5=| zoT?FPm#;i3Ds}yors+h)t6aRcoiMk;(ZB%Si;DL7_F0XN*q>D}E)Y`Hi>eO2NeOlR zT!f426fSK$4=*Yi;Qgp*-8$Ls+Qs0%b!!p z0PjIX`{9{ScmBkR5kEI7qo%jkF2=#6elb_UxOSEjgbZ-6t>I#fX6GM1thHig>T-7B zq1wzoY;8MUuyhh*+gdRuojkP30QcEbxwy9w7YDIic>EtYw>V&}omDDBl~aD>)=D;9 zp3kRrT~~e)0L4mPi&EhtvSYah*SK3cp)nOs)w7C*9Dvu!HRg)s+gw6_xeqK))`~IB z_)pB%QM=W%l8TiafZJ^u__{Y-L^30J*R*Y&Z0OA;VHhk_FTj2Lp5!$p;UZEPFz!7q z*P9gDQn9cI9`HIwO~1D*dT7|)_Mnclspn%}^6e~BZ&N-Yl4Z5^R2 zGPR$cWa8RwESn<{WtxD3C)?Q^NWh}bz) zRT|R`E#i^wZGm5A)a(iu{~0?wTtrspBAqsnE_0sTUqtG=4y)$T)eVU3>xLFtj?x|e zeEzt#F!*@%Bf-i+1d^5J{!7fP;Bsubbdt;yYD06BC;H7 zL&)HlHKyBDxrC4Tl_Tzx%Qc*F-SEqt9WLW_2pRmKJv*en>b1z1T@U7*kTIfaqTw=c zWU|RGoEs%f$l#a6_90sH%GV;_*iAizKx)Ia@{2s5PZ60$`G)*$Q`W3jF8}X4_m^di zvLy<>#+Fs+oTh2A+lyRf^P=Bt_p>K>>UtS8N5jRPBjxP*6~HH{>w5O~@XA#Vik8gu zmE3DxKgY1ijkn2#OAcMx!1NrwdI^`cFs33oaO?{)dJ z>0SsI;C^Gkgv%heC_9s zwf692#x=R+BnTJa{fv84pW)~lFQuQM%>_uywS``F{4ECBZD~*@T!1puZmscW?8@UW z4VbkeCA=}eyWAh+5+YoHylJ>c{5iVv_*+ykzSySQvF}B=0Ndr7Ag((8zYS*pX4rcf zlV37n?Izu3ZtgJQ0(^$q-qcrd-P_6SWHV!Jm|FyVp37g6T)6BVco*Bl7`in#nEa{vUQ$z6dGr|Rk+Z!-<`4hQ6TpYp$ z_%46%5H8`iB7_U@9c~}V%j*BY_qsJ;2p8bH-4-xxEdqSM$JNsWLg60RtV9QVuNy0v z;onEs?gj9^Zg1oT@Vy8a0GHXdb!_P|0KaJ#FsZdQOOFBgU5l+_{ePcv48ZSNydtGS zqVyPm-$WxXfXfgt04_to0Jsd@)Br9+H#LCE&`k~CGPiY8Q|tiW?{VkF2^pYFZk7ML zgxeq88ZaW)Z(9rCb;9SFZfXFRxjkTvY3{xS02jGEU;unC0tUck2p9mDAz%Pp=JsxC z0OttDpqzhx6>IITp(Ru>z!!K$xTuO4qr0v}zyKfa>Tp?JZwi@VBgOUW^NIv~uvdl4 zuu{Ub-SaR_!DL^TZV8xookszDn$oE@CC{L>!@yX7Sr*lF$WXxmKdAJ7@NHXFx4IrG8C8+C9UkOZRm3Y2fFf;M&;C1B zFwjj6;4%aZfXfgt04_to0JsbR1K={Z1War|4uJ1-TLr^Z0pR;F4hHx@R&4bNZvp=R X+7D3QSz-dY00000NkvXXu0mjfJO62q From 81a37328c7695bafd6bfec8c18d463a0a5fe96fd Mon Sep 17 00:00:00 2001 From: DebrisHauler Date: Fri, 17 Jan 2025 22:35:18 -0500 Subject: [PATCH 04/33] updated menu graphic --- assets/bigbug/menu/bb_menu0.png | Bin 5145 -> 5132 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/assets/bigbug/menu/bb_menu0.png b/assets/bigbug/menu/bb_menu0.png index 34c98657e30277ae5518e22105632df2b8c9c99d..9a3a720e1809c9783951e0e4ae409d666c43d1cc 100644 GIT binary patch delta 1575 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 Date: Sat, 18 Jan 2025 07:34:42 -0500 Subject: [PATCH 05/33] Quick play mode added. --- .../modes/games/bigbug/entityManager_bigbug.c | 8 + main/modes/games/bigbug/entity_bigbug.c | 160 +++++++++++++++++- main/modes/games/bigbug/entity_bigbug.h | 2 + main/modes/games/bigbug/mode_bigbug.c | 129 ++++++++++++-- main/modes/games/bigbug/typedef_bigbug.h | 3 +- 5 files changed, 281 insertions(+), 21 deletions(-) diff --git a/main/modes/games/bigbug/entityManager_bigbug.c b/main/modes/games/bigbug/entityManager_bigbug.c index acdd0b0e9..87ae8e033 100644 --- a/main/modes/games/bigbug/entityManager_bigbug.c +++ b/main/modes/games/bigbug/entityManager_bigbug.c @@ -1155,6 +1155,8 @@ bb_entity_t* bb_createEntity(bb_entityManager_t* entityManager, bb_animationType entityManager, LOOPING_ANIMATION, false, HARPOON, 3, (entity->pos.x >> DECIMAL_BITS) - 22, 0, false, false); // y position doesn't matter here. It will be handled in the update loop. + bb_clearCollisions(mData->cursor, false); + // This will make it draw pointed right ((bb_projectileData_t*)mData->cursor->data)->vel = (vec_t){10, 0}; @@ -1663,6 +1665,12 @@ bb_entity_t* bb_createEntity(bb_entityManager_t* entityManager, bb_animationType entity->drawFunction = &bb_drawGarbotnikUI; break; } + case BB_QUICKPLAY_CONFIRM: + { + entity->updateFunction = &bb_updateQuickplay; + entity->drawFunction = &bb_drawQuickplay; + break; + } default: // FLAME_ANIM and others need nothing set { break; diff --git a/main/modes/games/bigbug/entity_bigbug.c b/main/modes/games/bigbug/entity_bigbug.c index 5cb043510..ed79f32fa 100644 --- a/main/modes/games/bigbug/entity_bigbug.c +++ b/main/modes/games/bigbug/entity_bigbug.c @@ -1490,7 +1490,7 @@ void bb_updateBugShooting(bb_entity_t* self) { if (bb_randomInt(0, 100) < 1) { - bb_ensureEntitySpace(&self->gameData->entityManager, 1) + bb_ensureEntitySpace(&self->gameData->entityManager, 1); // call it paused and update frames in it's own update function because this one uses another spriteIdx. bb_entity_t* spit = bb_createEntity(&self->gameData->entityManager, LOOPING_ANIMATION, true, BB_SPIT, 10, self->pos.x >> DECIMAL_BITS, self->pos.y >> DECIMAL_BITS, true, false); @@ -1785,17 +1785,17 @@ void bb_updateFlyingBug(bb_entity_t* self) void bb_updateMenu(bb_entity_t* self) { bb_menuData_t* mData = (bb_menuData_t*)self->data; - if (self->gameData->btnDownState & PB_UP) + if (self->gameData->btnDownState & PB_UP && mData->selectionIdx < 2) { mData->selectionIdx--; mData->selectionIdx = mData->selectionIdx < 0 ? 1 : mData->selectionIdx; } - if (self->gameData->btnDownState & PB_DOWN) + if (self->gameData->btnDownState & PB_DOWN && mData->selectionIdx < 2) { mData->selectionIdx++; mData->selectionIdx = mData->selectionIdx > 1 ? 0 : mData->selectionIdx; } - if (self->gameData->btnDownState & PB_A) + if (self->gameData->btnDownState & PB_A && mData->selectionIdx < 2) { if (mData->selectionIdx == 0) { @@ -1848,9 +1848,14 @@ void bb_updateMenu(bb_entity_t* self) } else if (mData->selectionIdx == 1) { - // exit the game - self->gameData->exit = true; + // quik play + mData->selectionIdx = 2; + bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, BB_QUICKPLAY_CONFIRM, 1, + self->pos.x >> DECIMAL_BITS, self->pos.y >> DECIMAL_BITS, false, true); } + + self->gameData->btnState = 0; + self->gameData->btnDownState = 0; } mData->cursor->pos.y = self->pos.y - (209 << DECIMAL_BITS) + mData->selectionIdx * (22 << DECIMAL_BITS); @@ -3007,6 +3012,121 @@ void bb_updateSpaceLaser(bb_entity_t* self) self->pos.y = (slData->highestGarbage << 9) - self->halfHeight; } +void bb_updateQuickplay(bb_entity_t* self) +{ + bb_menuData_t* mData; + for(int eIdx = 0; eIdx < MAX_ENTITIES; eIdx++) + { + bb_entity_t* menu = &self->gameData->entityManager.entities[eIdx]; + if (menu != NULL && menu->spriteIndex == BB_MENU && menu->updateFunction != NULL) + { + mData = (bb_menuData_t*)menu->data; + break; + } + } + + if (self->gameData->btnDownState & PB_LEFT && mData->selectionIdx >= 2) + { + mData->selectionIdx--; + mData->selectionIdx = mData->selectionIdx < 2 ? 3 : mData->selectionIdx; + } + if (self->gameData->btnDownState & PB_RIGHT && mData->selectionIdx >= 2) + { + mData->selectionIdx++; + mData->selectionIdx = mData->selectionIdx > 3 ? 2 : mData->selectionIdx; + } + if (self->gameData->btnDownState & PB_A && mData->selectionIdx >= 2) + { + if (mData->selectionIdx == 2) + { + //no + //set selectionIdx to 1 + mData->selectionIdx = 1; + } + else if(mData->selectionIdx == 3) + { + //yes + //quick start the game + self->gameData->tutorialFlags = 255; + + uint32_t deathDumpsterX = (TILE_FIELD_WIDTH / 2) * TILE_SIZE + HALF_TILE - 1; + uint32_t deathDumpsterY = -2173; + + // create 3 rockets + for (int rocketIdx = 0; rocketIdx < 3; rocketIdx++) + { + self->gameData->entityManager.boosterEntities[rocketIdx] + = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, ROCKET_ANIM, 16, + deathDumpsterX - 96 + 96 * rocketIdx, deathDumpsterY, false, true); + + // bigbug->gameData.entityManager.boosterEntities[rocketIdx]->updateFunction = NULL; + + if (rocketIdx >= 1) + { + bb_rocketData_t* rData = (bb_rocketData_t*)self->gameData->entityManager.boosterEntities[rocketIdx]->data; + + rData->flame = bb_createEntity( + &(self->gameData->entityManager), LOOPING_ANIMATION, false, FLAME_ANIM, 6, + self->gameData->entityManager.boosterEntities[rocketIdx]->pos.x >> DECIMAL_BITS, + self->gameData->entityManager.boosterEntities[rocketIdx]->pos.y >> DECIMAL_BITS, true, false); + + rData->flame->updateFunction = &bb_updateFlame; + } + else // rocketIdx == 0 + { + self->gameData->entityManager.activeBooster = self->gameData->entityManager.boosterEntities[rocketIdx]; + ((bb_rocketData_t*)self->gameData->entityManager.activeBooster->data)->numDonuts = 20; + self->gameData->entityManager.activeBooster->currentAnimationFrame = 40; + self->gameData->entityManager.activeBooster->pos.y = 50; + self->gameData->entityManager.activeBooster->updateFunction = bb_updateHeavyFalling; + bb_entity_t* arm = bb_createEntity( + &self->gameData->entityManager, NO_ANIMATION, true, ATTACHMENT_ARM, 1, + self->gameData->entityManager.activeBooster->pos.x >> DECIMAL_BITS, + (self->gameData->entityManager.activeBooster->pos.y >> DECIMAL_BITS) - 33, true, false); + ((bb_attachmentArmData_t*)arm->data)->rocket = self->gameData->entityManager.activeBooster; + + bb_entity_t* grabbyHand = bb_createEntity( + &self->gameData->entityManager, LOOPING_ANIMATION, true, BB_GRABBY_HAND, 6, + self->gameData->entityManager.activeBooster->pos.x >> DECIMAL_BITS, + (self->gameData->entityManager.activeBooster->pos.y >> DECIMAL_BITS) - 53, true, false); + ((bb_grabbyHandData_t*)grabbyHand->data)->rocket = self->gameData->entityManager.activeBooster; + } + } + + // create the death dumpster + self->gameData->entityManager.deathDumpster + = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, BB_DEATH_DUMPSTER, 1, deathDumpsterX, + deathDumpsterY, false, true); + + // create garbotnik's UI + bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, BB_GARBOTNIK_UI, 1, 0, 0, false, true); + + self->gameData->entityManager.viewEntity + = bb_createEntity(&(self->gameData->entityManager), NO_ANIMATION, true, GARBOTNIK_FLYING, 1, + self->gameData->entityManager.activeBooster->pos.x >> DECIMAL_BITS, + (self->gameData->entityManager.activeBooster->pos.y >> DECIMAL_BITS) - 90, true, false); + + bb_loadWsgs(&self->gameData->tilemap); + + self->gameData->entityManager.playerEntity = self->gameData->entityManager.viewEntity; + + int8_t oldBoosterYs[3] = {-1, -1, -1}; + bb_generateWorld(&(self->gameData->tilemap), oldBoosterYs); + + // Set the mode to game mode + self->gameData->screen = BIGBUG_GAME; + + bb_setupMidi(); + unloadMidiFile(&self->gameData->bgm); + loadMidiFile("BigBugExploration.mid", &self->gameData->bgm, true); + globalMidiPlayerPlaySong(&self->gameData->bgm, MIDI_BGM); + self->gameData->camera.camera.pos = (vec_t){0, 0}; + } + // destroy self + bb_destroyEntity(self, false, false); + } +} + void bb_drawGarbotnikFlying(bb_entityManager_t* entityManager, rectangle_t* camera, bb_entity_t* self) { if (GARBOTNIK_DATA != self->dataType) @@ -4094,6 +4214,34 @@ void bb_drawDeadBug(bb_entityManager_t* entityManager, rectangle_t* camera, bb_e false, true, 0); } +void bb_drawQuickplay(bb_entityManager_t* entityManager, rectangle_t* camera, bb_entity_t* self) +{ + bb_menuData_t* mData; + for(int eIdx = 0; eIdx < MAX_ENTITIES; eIdx++) + { + bb_entity_t* menu = &entityManager->entities[eIdx]; + if (menu != NULL && menu->spriteIndex == BB_MENU && menu->updateFunction != NULL) + { + mData = (bb_menuData_t*)menu->data; + break; + } + } + + drawRectFilled(0, 0, TFT_WIDTH, TFT_HEIGHT, c000); + int16_t xOffBecauseTheApiIsDumb = 20; + int16_t yOffBecauseTheApiIsDumb = 5; + + drawTextWordWrapCentered(&self->gameData->font, c444, "Quick Play mode skips the intro sequence and all tutorial dialogue. It might also harvest the soul of one volunteer developer. Are you sure you want to continue?", &xOffBecauseTheApiIsDumb, &yOffBecauseTheApiIsDumb, TFT_WIDTH - 5, TFT_HEIGHT - 5); + drawRect((TFT_WIDTH >> 2) - 40, TFT_HEIGHT >> 1, (TFT_WIDTH >> 2) + 40, (TFT_HEIGHT >> 1) + 40, mData->selectionIdx == 2 ? c550 : c444); + xOffBecauseTheApiIsDumb = (TFT_WIDTH >> 2) - 40; + yOffBecauseTheApiIsDumb = (TFT_HEIGHT >> 1) + 15; + drawTextWordWrapCentered(&self->gameData->font, mData->selectionIdx == 2 ? c550 : c444, "NO", &xOffBecauseTheApiIsDumb, &yOffBecauseTheApiIsDumb, (TFT_WIDTH >> 2) + 40, (TFT_HEIGHT >> 1) + 60); + drawRect((TFT_WIDTH >> 1) + (TFT_WIDTH >> 2) - 40, TFT_HEIGHT >> 1, (TFT_WIDTH >> 1) + (TFT_WIDTH >> 2) + 40, (TFT_HEIGHT >> 1) + 40, mData->selectionIdx == 3 ? c550 : c444); + xOffBecauseTheApiIsDumb = (TFT_WIDTH >> 1) + (TFT_WIDTH >> 2) - 40; + yOffBecauseTheApiIsDumb = (TFT_HEIGHT >> 1) + 15; + drawTextWordWrapCentered(&self->gameData->font, mData->selectionIdx == 3 ? c550 : c444, "YES", &xOffBecauseTheApiIsDumb, &yOffBecauseTheApiIsDumb, (TFT_WIDTH >> 1) + (TFT_WIDTH >> 2) + 40, (TFT_HEIGHT >> 1) + 60); +} + // void bb_drawRect(bb_entityManager_t* entityManager, rectangle_t* camera, bb_entity_t* self) // { // drawRect (((self->pos.x - self->halfWidth) >>DECIMAL_BITS) - camera->pos.x, diff --git a/main/modes/games/bigbug/entity_bigbug.h b/main/modes/games/bigbug/entity_bigbug.h index 9e2c6f5df..04fbe84a6 100644 --- a/main/modes/games/bigbug/entity_bigbug.h +++ b/main/modes/games/bigbug/entity_bigbug.h @@ -446,6 +446,7 @@ void bb_updateDrillBot(bb_entity_t* self); void bb_updateTimedPhysicsObject(bb_entity_t* self); void bb_updatePacifier(bb_entity_t* self); void bb_updateSpaceLaser(bb_entity_t* self); +void bb_updateQuickplay(bb_entity_t* self); void bb_drawGarbotnikFlying(bb_entityManager_t* entityManager, rectangle_t* camera, bb_entity_t* self); void bb_drawHarpoon(bb_entityManager_t* entityManager, rectangle_t* camera, bb_entity_t* self); @@ -480,6 +481,7 @@ void bb_drawPacifier(bb_entityManager_t* entityManager, rectangle_t* camera, bb_ void bb_drawSpaceLaser(bb_entityManager_t* entityManager, rectangle_t* camera, bb_entity_t* self); void bb_drawDeadBug(bb_entityManager_t* entityManager, rectangle_t* camera, bb_entity_t* self); void bb_drawGarbotnikUI(bb_entityManager_t* entityManager, rectangle_t* camera, bb_entity_t* self); +void bb_drawQuickplay(bb_entityManager_t* entityManager, rectangle_t* camera, bb_entity_t* self); // void bb_drawRect(bb_entityManager_t* entityManager, rectangle_t* camera, bb_entity_t* self); diff --git a/main/modes/games/bigbug/mode_bigbug.c b/main/modes/games/bigbug/mode_bigbug.c index fa9b8261a..66514fc21 100644 --- a/main/modes/games/bigbug/mode_bigbug.c +++ b/main/modes/games/bigbug/mode_bigbug.c @@ -72,7 +72,6 @@ static void bb_DrawScene_Garbotnik_Upgrade(void); static void bb_GameLoop_Garbotnik_Upgrade(int64_t elapsedUs); static void bb_GameLoop_Loadout_Select(int64_t elapsedUs); static void bb_GameLoop(int64_t elapsedUs); -static void bb_Reset(void); static void bb_SetLeds(void); static void bb_setPrimingLeds(uint8_t primingEffect); static void bb_UpdateTileSupport(void); @@ -204,8 +203,6 @@ static void bb_EnterMode(void) bb_setupMidi(); soundPlayBgm(&bigbug->gameData.bgm, MIDI_BGM); - - bb_Reset(); } #else @@ -323,12 +320,125 @@ static void bb_EnterModeSkipIntro(void) unloadMidiFile(&bigbug->gameData.bgm); loadMidiFile("BigBugExploration.mid", &bigbug->gameData.bgm, true); globalMidiPlayerPlaySong(&bigbug->gameData.bgm, MIDI_BGM); - - bb_Reset(); } #endif +void bb_quickPlay(void) +{ + setFrameRateUs(16667); // 60 FPS + + // Force draw a loading screen + fillDisplayArea(0, 0, TFT_WIDTH, TFT_HEIGHT, c123); + + // Allocate memory for the game state + bigbug = heap_caps_calloc(1, sizeof(bb_t), MALLOC_CAP_SPIRAM); + + // calloc the columns in layers separately to avoid a big alloc + for (int32_t w = 0; w < TILE_FIELD_WIDTH; w++) + { + bigbug->gameData.tilemap.fgTiles[w] + = heap_caps_calloc(TILE_FIELD_HEIGHT, sizeof(bb_foregroundTileInfo_t), MALLOC_CAP_SPIRAM); + bigbug->gameData.tilemap.mgTiles[w] + = heap_caps_calloc(TILE_FIELD_HEIGHT, sizeof(bb_midgroundTileInfo_t), MALLOC_CAP_SPIRAM); + } + + // Allocate WSG loading helpers + bb_hsd = heatshrink_decoder_alloc(256, 8, 4); + // The largest image is bb_menu2.png, decodes to 99124 bytes + // 99328 is 1024 * 97 + bb_decodeSpace = heap_caps_malloc(99328, MALLOC_CAP_SPIRAM); + + bb_SetLeds(); + + // Load font + loadFont("ibm_vga8.font", &bigbug->gameData.font, false); + + const char loadingStr[] = "Loading..."; + int32_t tWidth = textWidth(&bigbug->gameData.font, loadingStr); + drawText(&bigbug->gameData.font, c542, loadingStr, (TFT_WIDTH - tWidth) / 2, + (TFT_HEIGHT - bigbug->gameData.font.height) / 2); + drawDisplayTft(NULL); + + bb_initializeGameData(&bigbug->gameData); + bb_initializeEntityManager(&bigbug->gameData.entityManager, &bigbug->gameData); + // Shrink after loading initial sprites. The next largest image is ovo_talk0.png, decodes to 67200 bytes + // 67584 is 1024 * 66 + bb_decodeSpace = heap_caps_realloc(bb_decodeSpace, 67584, MALLOC_CAP_SPIRAM); + + uint32_t deathDumpsterX = (TILE_FIELD_WIDTH / 2) * TILE_SIZE + HALF_TILE - 1; + uint32_t deathDumpsterY = -2173; + + // create 3 rockets + for (int rocketIdx = 0; rocketIdx < 3; rocketIdx++) + { + bigbug->gameData.entityManager.boosterEntities[rocketIdx] + = bb_createEntity(&bigbug->gameData.entityManager, NO_ANIMATION, true, ROCKET_ANIM, 16, + deathDumpsterX - 96 + 96 * rocketIdx, deathDumpsterY, false, true); + + // bigbug->gameData.entityManager.boosterEntities[rocketIdx]->updateFunction = NULL; + + if (rocketIdx >= 1) + { + bb_rocketData_t* rData = (bb_rocketData_t*)bigbug->gameData.entityManager.boosterEntities[rocketIdx]->data; + + rData->flame = bb_createEntity( + &(bigbug->gameData.entityManager), LOOPING_ANIMATION, false, FLAME_ANIM, 6, + bigbug->gameData.entityManager.boosterEntities[rocketIdx]->pos.x >> DECIMAL_BITS, + bigbug->gameData.entityManager.boosterEntities[rocketIdx]->pos.y >> DECIMAL_BITS, true, false); + + rData->flame->updateFunction = &bb_updateFlame; + } + else // rocketIdx == 0 + { + bigbug->gameData.entityManager.activeBooster = bigbug->gameData.entityManager.boosterEntities[rocketIdx]; + ((bb_rocketData_t*)bigbug->gameData.entityManager.activeBooster->data)->numDonuts = 20; + bigbug->gameData.entityManager.activeBooster->currentAnimationFrame = 40; + bigbug->gameData.entityManager.activeBooster->pos.y = 50; + bigbug->gameData.entityManager.activeBooster->updateFunction = bb_updateHeavyFalling; + bb_entity_t* arm = bb_createEntity( + &bigbug->gameData.entityManager, NO_ANIMATION, true, ATTACHMENT_ARM, 1, + bigbug->gameData.entityManager.activeBooster->pos.x >> DECIMAL_BITS, + (bigbug->gameData.entityManager.activeBooster->pos.y >> DECIMAL_BITS) - 33, true, false); + ((bb_attachmentArmData_t*)arm->data)->rocket = bigbug->gameData.entityManager.activeBooster; + + bb_entity_t* grabbyHand = bb_createEntity( + &bigbug->gameData.entityManager, LOOPING_ANIMATION, true, BB_GRABBY_HAND, 6, + bigbug->gameData.entityManager.activeBooster->pos.x >> DECIMAL_BITS, + (bigbug->gameData.entityManager.activeBooster->pos.y >> DECIMAL_BITS) - 53, true, false); + ((bb_grabbyHandData_t*)grabbyHand->data)->rocket = bigbug->gameData.entityManager.activeBooster; + } + } + + // create the death dumpster + bigbug->gameData.entityManager.deathDumpster + = bb_createEntity(&bigbug->gameData.entityManager, NO_ANIMATION, true, BB_DEATH_DUMPSTER, 1, deathDumpsterX, + deathDumpsterY, false, true); + + // create garbotnik's UI + bb_createEntity(&bigbug->gameData.entityManager, NO_ANIMATION, true, BB_GARBOTNIK_UI, 1, 0, 0, false, true); + + bigbug->gameData.entityManager.viewEntity + = bb_createEntity(&(bigbug->gameData.entityManager), NO_ANIMATION, true, GARBOTNIK_FLYING, 1, + bigbug->gameData.entityManager.activeBooster->pos.x >> DECIMAL_BITS, + (bigbug->gameData.entityManager.activeBooster->pos.y >> DECIMAL_BITS) - 90, true, false); + + bb_loadWsgs(&bigbug->gameData.tilemap); + + bigbug->gameData.entityManager.playerEntity = bigbug->gameData.entityManager.viewEntity; + + int8_t oldBoosterYs[3] = {-1, -1, -1}; + bb_generateWorld(&(bigbug->gameData.tilemap), oldBoosterYs); + + // Set the mode to game mode + bigbug->gameData.screen = BIGBUG_GAME; + + bb_setupMidi(); + unloadMidiFile(&bigbug->gameData.bgm); + loadMidiFile("BigBugExploration.mid", &bigbug->gameData.bgm, true); + globalMidiPlayerPlaySong(&bigbug->gameData.bgm, MIDI_BGM); +} + void bb_setupMidi(void) { // Setup MIDI @@ -1672,15 +1782,6 @@ static void bb_GameLoop(int64_t elapsedUs) // ESP_LOGD(BB_TAG,"FPS: %ld\n", 1000000 / elapsedUs); } -static void bb_Reset(void) -{ - ESP_LOGD(BB_TAG, "The width is: %d\n", FIELD_WIDTH); - ESP_LOGD(BB_TAG, "The height is: %d\n", FIELD_HEIGHT); - - bigbug->gameData.camera.camera.width = FIELD_WIDTH; - bigbug->gameData.camera.camera.height = FIELD_HEIGHT; -} - /** * @brief Set the LEDs */ diff --git a/main/modes/games/bigbug/typedef_bigbug.h b/main/modes/games/bigbug/typedef_bigbug.h index ce03c2600..8d94a8042 100644 --- a/main/modes/games/bigbug/typedef_bigbug.h +++ b/main/modes/games/bigbug/typedef_bigbug.h @@ -83,6 +83,7 @@ typedef enum BB_SPACE_LASER, // A beam of pure damage straight down from the sky. BB_BRICK_TUTORIAL, // A particular spot on levels 3 & 4 that makes garbotnik talk about breaking bricks. BB_GARBOTNIK_UI, // The UI that shows the harpooon count and wile calldowns & cooldowns. + BB_QUICKPLAY_CONFIRM, // A screen that asks if you want to play quickplay. } bb_spriteDef_t; typedef enum @@ -104,7 +105,7 @@ typedef enum { ONESHOT_ANIMATION, LOOPING_ANIMATION, - NO_ANIMATION + NO_ANIMATION, } bb_animationType_t; void bb_trigger501kg(bb_entity_t* self); From 7d04243c400d32934a6eea70c63a636864a8605d Mon Sep 17 00:00:00 2001 From: DebrisHauler Date: Sat, 18 Jan 2025 20:49:57 -0500 Subject: [PATCH 06/33] Turn down the edge 10%. Fix quick play render order issue. Bring back the star field by setting camera width and height (woops). --- .../modes/games/bigbug/entityManager_bigbug.c | 13 ++++------ main/modes/games/bigbug/entity_bigbug.c | 24 +++++++++---------- main/modes/games/bigbug/mode_bigbug.c | 2 ++ 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/main/modes/games/bigbug/entityManager_bigbug.c b/main/modes/games/bigbug/entityManager_bigbug.c index 87ae8e033..ca180ab59 100644 --- a/main/modes/games/bigbug/entityManager_bigbug.c +++ b/main/modes/games/bigbug/entityManager_bigbug.c @@ -868,7 +868,7 @@ bb_entity_t* bb_createEntity(bb_entityManager_t* entityManager, bb_animationType entity->gameData->camera.camera.pos.x, entity->gameData->camera.camera.pos.y, false, true); - bb_dialogueData_t* dData = bb_createDialogueData(13, "Ovo"); + bb_dialogueData_t* dData = bb_createDialogueData(11, "Ovo"); bb_setCharacterLine(dData, 0, "Ovo", "Wow, look at that juicy bug!"); bb_setCharacterLine(dData, 1, "Ovo", "Ah, who am I kidding?"); @@ -880,16 +880,13 @@ bb_entity_t* bb_createEntity(bb_entityManager_t* entityManager, bb_animationType "There are a bunch of ways to incapacitate that bug. But let's learn how to use harpoons first."); bb_setCharacterLine(dData, 5, "Ovo", "Hold your right thumb on the C-Touchpad to aim."); bb_setCharacterLine(dData, 6, "Ovo", "Hey! Wait until I'm done talking, you doofus."); - bb_setCharacterLine(dData, 7, "Ovo", "MAGFest attendees are the actual worst."); - bb_setCharacterLine(dData, 8, "Ovo", - "Don't take it personally, but I can detect your stench through the swadge's smelliphone."); - bb_setCharacterLine(dData, 9, "Ovo", + bb_setCharacterLine(dData, 7, "Ovo", "If your touch vector is outside of the purple circle that appears on-screen, harpoons " "will fire steadily."); - bb_setCharacterLine(dData, 10, "Ovo", "Three hits will flip the bug upside down!"); - bb_setCharacterLine(dData, 11, "Ovo", + bb_setCharacterLine(dData, 8, "Ovo", "Three hits will flip the bug upside down!"); + bb_setCharacterLine(dData, 9, "Ovo", "There's a nifty detail where harpoons with upward velocity pass right through terrain."); - bb_setCharacterLine(dData, 12, "Ovo", "Now, let's get that bug!"); + bb_setCharacterLine(dData, 10, "Ovo", "Now, let's get that bug!"); dData->curString = -1; dData->endDialogueCB = &bb_afterGarbotnikTutorialTalk; diff --git a/main/modes/games/bigbug/entity_bigbug.c b/main/modes/games/bigbug/entity_bigbug.c index ed79f32fa..0619bc043 100644 --- a/main/modes/games/bigbug/entity_bigbug.c +++ b/main/modes/games/bigbug/entity_bigbug.c @@ -1848,10 +1848,10 @@ void bb_updateMenu(bb_entity_t* self) } else if (mData->selectionIdx == 1) { - // quik play + // quick play mData->selectionIdx = 2; bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, BB_QUICKPLAY_CONFIRM, 1, - self->pos.x >> DECIMAL_BITS, self->pos.y >> DECIMAL_BITS, false, true); + self->pos.x >> DECIMAL_BITS, self->pos.y >> DECIMAL_BITS, true, true); } self->gameData->btnState = 0; @@ -4228,18 +4228,18 @@ void bb_drawQuickplay(bb_entityManager_t* entityManager, rectangle_t* camera, bb } drawRectFilled(0, 0, TFT_WIDTH, TFT_HEIGHT, c000); - int16_t xOffBecauseTheApiIsDumb = 20; - int16_t yOffBecauseTheApiIsDumb = 5; + int16_t xOff = 20; + int16_t yOff = 30; - drawTextWordWrapCentered(&self->gameData->font, c444, "Quick Play mode skips the intro sequence and all tutorial dialogue. It might also harvest the soul of one volunteer developer. Are you sure you want to continue?", &xOffBecauseTheApiIsDumb, &yOffBecauseTheApiIsDumb, TFT_WIDTH - 5, TFT_HEIGHT - 5); + drawTextWordWrapCentered(&self->gameData->font, c444, "Quick Play mode skips the intro sequence and all tutorial dialogue. Are you sure you want to continue?", &xOff, &yOff, TFT_WIDTH - 5, TFT_HEIGHT - 5); drawRect((TFT_WIDTH >> 2) - 40, TFT_HEIGHT >> 1, (TFT_WIDTH >> 2) + 40, (TFT_HEIGHT >> 1) + 40, mData->selectionIdx == 2 ? c550 : c444); - xOffBecauseTheApiIsDumb = (TFT_WIDTH >> 2) - 40; - yOffBecauseTheApiIsDumb = (TFT_HEIGHT >> 1) + 15; - drawTextWordWrapCentered(&self->gameData->font, mData->selectionIdx == 2 ? c550 : c444, "NO", &xOffBecauseTheApiIsDumb, &yOffBecauseTheApiIsDumb, (TFT_WIDTH >> 2) + 40, (TFT_HEIGHT >> 1) + 60); + xOff = (TFT_WIDTH >> 2) - 40; + yOff = (TFT_HEIGHT >> 1) + 15; + drawTextWordWrapCentered(&self->gameData->font, mData->selectionIdx == 2 ? c550 : c444, "NO", &xOff, &yOff, (TFT_WIDTH >> 2) + 40, (TFT_HEIGHT >> 1) + 60); drawRect((TFT_WIDTH >> 1) + (TFT_WIDTH >> 2) - 40, TFT_HEIGHT >> 1, (TFT_WIDTH >> 1) + (TFT_WIDTH >> 2) + 40, (TFT_HEIGHT >> 1) + 40, mData->selectionIdx == 3 ? c550 : c444); - xOffBecauseTheApiIsDumb = (TFT_WIDTH >> 1) + (TFT_WIDTH >> 2) - 40; - yOffBecauseTheApiIsDumb = (TFT_HEIGHT >> 1) + 15; - drawTextWordWrapCentered(&self->gameData->font, mData->selectionIdx == 3 ? c550 : c444, "YES", &xOffBecauseTheApiIsDumb, &yOffBecauseTheApiIsDumb, (TFT_WIDTH >> 1) + (TFT_WIDTH >> 2) + 40, (TFT_HEIGHT >> 1) + 60); + xOff = (TFT_WIDTH >> 1) + (TFT_WIDTH >> 2) - 40; + yOff = (TFT_HEIGHT >> 1) + 15; + drawTextWordWrapCentered(&self->gameData->font, mData->selectionIdx == 3 ? c550 : c444, "YES", &xOff, &yOff, (TFT_WIDTH >> 1) + (TFT_WIDTH >> 2) + 40, (TFT_HEIGHT >> 1) + 60); } // void bb_drawRect(bb_entityManager_t* entityManager, rectangle_t* camera, bb_entity_t* self) @@ -4804,7 +4804,7 @@ void bb_onCollisionBrickTutorial(bb_entity_t* self, bb_entity_t* other, bb_hitIn dData, 6, "Ovo", "\"Same as the other garbage densities, bricks will crumble if they are totally disconnected.\""); bb_setCharacterLine(dData, 7, "Ovo", "\"Or they can be smashed easily with heavy falling objects.\""); - bb_setCharacterLine(dData, 8, "Ovo", "\"Big Bug's early-game design is all about trading fuel for upgrades.\""); + bb_setCharacterLine(dData, 8, "Ovo", "\"Big Bug's early-game design is all about using your fuel to get upgrades.\""); bb_setCharacterLine(dData, 9, "Ovo", "\"So be sure to strategize how to use your time!\""); bb_setCharacterLine(dData, 10, "Ovo", "\"The late-game design has a shift in focus away from fuel.\""); bb_setCharacterLine(dData, 11, "Ovo", "\"You'll just have to see it for yourself!\""); diff --git a/main/modes/games/bigbug/mode_bigbug.c b/main/modes/games/bigbug/mode_bigbug.c index 66514fc21..b4d15f63f 100644 --- a/main/modes/games/bigbug/mode_bigbug.c +++ b/main/modes/games/bigbug/mode_bigbug.c @@ -190,6 +190,8 @@ static void bb_EnterMode(void) bigbug->gameData.camera.camera.pos.x = (bigbug->gameData.entityManager.viewEntity->pos.x >> DECIMAL_BITS) - 140; bigbug->gameData.camera.camera.pos.y = (bigbug->gameData.entityManager.viewEntity->pos.y >> DECIMAL_BITS) - 120; + bigbug->gameData.camera.camera.width = FIELD_WIDTH; + bigbug->gameData.camera.camera.height = FIELD_HEIGHT; ((bb_goToData*)bigbug->gameData.entityManager.viewEntity->data)->executeOnArrival = &bb_startGarbotnikIntro; From e8fab27e43b30697a849179dd5d4ca77ff0221d3 Mon Sep 17 00:00:00 2001 From: DebrisHauler Date: Sat, 18 Jan 2025 20:58:15 -0500 Subject: [PATCH 07/33] Zero out some pathfinding data on trash day. Recalculate the active entity count every single day. --- main/modes/games/bigbug/entity_bigbug.c | 12 ++++++------ main/modes/games/bigbug/worldGen_bigbug.c | 7 ++++--- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/main/modes/games/bigbug/entity_bigbug.c b/main/modes/games/bigbug/entity_bigbug.c index 0619bc043..ae63048b1 100644 --- a/main/modes/games/bigbug/entity_bigbug.c +++ b/main/modes/games/bigbug/entity_bigbug.c @@ -326,12 +326,12 @@ void bb_updateRocketLiftoff(bb_entity_t* self) } } bb_generateWorld(&(self->gameData->tilemap), oldBoosterYs); - // brute force recalculate activeEntities count to clean up any inaccuracies. - self->gameData->entityManager.activeEntities = 0; - for (int i = 0; i < MAX_ENTITIES; i++) - { - self->gameData->entityManager.activeEntities += self->gameData->entityManager.entities[i].active; - } + } + // brute force recalculate activeEntities count to clean up any inaccuracies. + self->gameData->entityManager.activeEntities = 0; + for (int i = 0; i < MAX_ENTITIES; i++) + { + self->gameData->entityManager.activeEntities += self->gameData->entityManager.entities[i].active; } bb_entity_t* ovo diff --git a/main/modes/games/bigbug/worldGen_bigbug.c b/main/modes/games/bigbug/worldGen_bigbug.c index 80dfea5e7..19e90bd8e 100644 --- a/main/modes/games/bigbug/worldGen_bigbug.c +++ b/main/modes/games/bigbug/worldGen_bigbug.c @@ -36,12 +36,13 @@ void bb_generateWorld(bb_tilemap_t* tilemap, int8_t* oldBoosterYs) for (int j = 0; j < TILE_FIELD_HEIGHT; j++) { tilemap->fgTiles[i][j].pos = i | (j << 7) | (1 << 15); - tilemap->mgTiles[i][j].pos = i | (j << 7); // z is implicitly zero - tilemap->fgTiles[i][j].embed = NOTHING_EMBED; - tilemap->fgTiles[i][j].entity = NULL; + tilemap->fgTiles[i][j].gCost = 0; + tilemap->mgTiles[i][j].gCost = 0; + tilemap->fgTiles[i][j].hCost = 0; + tilemap->mgTiles[i][j].hCost = 0; uint32_t rgbCol = paletteToRGB(levelWsg.px[(j * levelWsg.w) + i]); From 857d23cd175d44a309a03e86cd5752cb9e463d73 Mon Sep 17 00:00:00 2001 From: DebrisHauler Date: Sat, 18 Jan 2025 21:14:43 -0500 Subject: [PATCH 08/33] Better sprite loading and unloading of the foodcart pieces. Should be fool proof now. --- main/modes/games/bigbug/entityManager_bigbug.c | 6 +----- main/modes/games/bigbug/entity_bigbug.c | 17 +++++++---------- main/modes/games/bigbug/entity_bigbug.h | 1 - main/modes/games/bigbug/mode_bigbug.c | 1 - main/modes/games/bigbug/mode_bigbug.h | 1 + 5 files changed, 9 insertions(+), 17 deletions(-) diff --git a/main/modes/games/bigbug/entityManager_bigbug.c b/main/modes/games/bigbug/entityManager_bigbug.c index ca180ab59..59fbc91d1 100644 --- a/main/modes/games/bigbug/entityManager_bigbug.c +++ b/main/modes/games/bigbug/entityManager_bigbug.c @@ -295,11 +295,7 @@ void bb_updateEntities(bb_entityManager_t* entityManager, bb_camera_t* camera) bb_foodCartData_t* fcData = (bb_foodCartData_t*)curEntity->data; // tell this partner of the change in address ((bb_foodCartData_t*)fcData->partner->data)->partner = foundSpot; - if (((bb_foodCartData_t*)fcData->partner->data)->isCached) - { - bb_loadSprite("foodCart", 2, 1, &entityManager->sprites[BB_FOOD_CART]); - } - fcData->isCached = false; + bb_loadSprite("foodCart", 2, 1, &entityManager->sprites[BB_FOOD_CART]); break; } default: diff --git a/main/modes/games/bigbug/entity_bigbug.c b/main/modes/games/bigbug/entity_bigbug.c index ae63048b1..655027b9d 100644 --- a/main/modes/games/bigbug/entity_bigbug.c +++ b/main/modes/games/bigbug/entity_bigbug.c @@ -78,16 +78,14 @@ void bb_destroyEntity(bb_entity_t* self, bool caching, bool wasInTheMainArray) } else if (self->spriteIndex == BB_FOOD_CART) { - bb_foodCartData_t* fcData = (bb_foodCartData_t*)self->data; - // The food cart needs to track its own caching status to communicate just-in-time loading between both - // pieces. - fcData->isCached = caching; - if (fcData->partner->active == false || ((bb_foodCartData_t*)fcData->partner->data)->isCached) + uint8_t thisFrame = self->currentAnimationFrame; + if(self->currentAnimationFrame > 1) { - for (int frame = 0; frame < self->gameData->entityManager.sprites[self->spriteIndex].numFrames; frame++) - { - freeWsg(&self->gameData->entityManager.sprites[self->spriteIndex].frames[frame]); - } + thisFrame = 1; + } + if(caching && (self->gameData->entityManager.sprites[self->spriteIndex].frames[thisFrame].w || self->gameData->entityManager.sprites[self->spriteIndex].frames[thisFrame].h)) + { + freeWsg(&self->gameData->entityManager.sprites[self->spriteIndex].frames[thisFrame]); } } @@ -2756,7 +2754,6 @@ void bb_updateExplosion(bb_entity_t* self) // tell this partner of the change in address ((bb_foodCartData_t*)fcData->partner->data)->partner = foundSpot; bb_loadSprite("foodCart", 2, 1, &self->gameData->entityManager.sprites[BB_FOOD_CART]); - fcData->isCached = false; } continue; } diff --git a/main/modes/games/bigbug/entity_bigbug.h b/main/modes/games/bigbug/entity_bigbug.h index 04fbe84a6..dfeb7d684 100644 --- a/main/modes/games/bigbug/entity_bigbug.h +++ b/main/modes/games/bigbug/entity_bigbug.h @@ -262,7 +262,6 @@ typedef struct bb_entity_t* jankyBugDig[6]; // When a bug collides with this, the dirt "digs" toward the car fight arena bb_spriteDef_t reward; // The sprite to spawn when the food cart is destroyed. bb_entity_t* partner; // the other piece of the food cart - bool isCached; // tracking this to only unload sprites when both pieces are cached. int8_t damageEffect; // decrements over time. Render damagePalette color swap if > 0, and if this cart is not the // zero animation frame because the cart background gets not graphical effect. } bb_foodCartData_t; diff --git a/main/modes/games/bigbug/mode_bigbug.c b/main/modes/games/bigbug/mode_bigbug.c index b4d15f63f..c1d3c6e16 100644 --- a/main/modes/games/bigbug/mode_bigbug.c +++ b/main/modes/games/bigbug/mode_bigbug.c @@ -394,7 +394,6 @@ void bb_quickPlay(void) else // rocketIdx == 0 { bigbug->gameData.entityManager.activeBooster = bigbug->gameData.entityManager.boosterEntities[rocketIdx]; - ((bb_rocketData_t*)bigbug->gameData.entityManager.activeBooster->data)->numDonuts = 20; bigbug->gameData.entityManager.activeBooster->currentAnimationFrame = 40; bigbug->gameData.entityManager.activeBooster->pos.y = 50; bigbug->gameData.entityManager.activeBooster->updateFunction = bb_updateHeavyFalling; diff --git a/main/modes/games/bigbug/mode_bigbug.h b/main/modes/games/bigbug/mode_bigbug.h index b2d63e57c..0ab3ac9cb 100644 --- a/main/modes/games/bigbug/mode_bigbug.h +++ b/main/modes/games/bigbug/mode_bigbug.h @@ -14,6 +14,7 @@ #ifndef _MODE_BIGBUG_H_ #define _MODE_BIGBUG_H_ +void bb_quickPlay(void); void bb_setupMidi(void); void bb_FreeTilemapData(void); From 0aaf7f576831b55a1fe70b35a640ff72e393a186 Mon Sep 17 00:00:00 2001 From: DebrisHauler Date: Sat, 18 Jan 2025 21:50:06 -0500 Subject: [PATCH 09/33] gabby hand animates again. --- main/modes/games/bigbug/entity_bigbug.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/main/modes/games/bigbug/entity_bigbug.c b/main/modes/games/bigbug/entity_bigbug.c index 655027b9d..409b05378 100644 --- a/main/modes/games/bigbug/entity_bigbug.c +++ b/main/modes/games/bigbug/entity_bigbug.c @@ -495,8 +495,8 @@ void bb_updateGarbotnikDeploy(bb_entity_t* self) ((bb_attachmentArmData_t*)arm->data)->rocket = self; bb_entity_t* grabbyHand - = bb_createEntity(&self->gameData->entityManager, LOOPING_ANIMATION, true, BB_GRABBY_HAND, 5, - self->pos.x >> DECIMAL_BITS, (self->pos.y >> DECIMAL_BITS) - 53, false, false); + = bb_createEntity(&self->gameData->entityManager, LOOPING_ANIMATION, true, BB_GRABBY_HAND, 6, + self->pos.x >> DECIMAL_BITS, (self->pos.y >> DECIMAL_BITS) - 53, true, false); ((bb_grabbyHandData_t*)grabbyHand->data)->rocket = self; self->paused = true; @@ -3119,6 +3119,8 @@ void bb_updateQuickplay(bb_entity_t* self) globalMidiPlayerPlaySong(&self->gameData->bgm, MIDI_BGM); self->gameData->camera.camera.pos = (vec_t){0, 0}; } + self->gameData->btnDownState = 0; + self->gameData->btnState = 0; // destroy self bb_destroyEntity(self, false, false); } @@ -3772,7 +3774,7 @@ void bb_drawGrabbyHand(bb_entityManager_t* entityManager, rectangle_t* camera, b // don't draw the hand if it is fully retracted. Cuts down on overdraw a lot of the time. if (self->gameData->entityManager.sprites[BB_GRABBY_HAND].originY > -26) { - drawWsgSimple(&entityManager->sprites[self->spriteIndex].frames[0], + drawWsgSimple(&entityManager->sprites[self->spriteIndex].frames[self->currentAnimationFrame], (self->pos.x >> DECIMAL_BITS) - entityManager->sprites[self->spriteIndex].originX - camera->pos.x, (self->pos.y >> DECIMAL_BITS) - entityManager->sprites[self->spriteIndex].originY - camera->pos.y); @@ -4556,10 +4558,11 @@ void bb_onCollisionGrabbyHand(bb_entity_t* self, bb_entity_t* other, bb_hitInfo_ // if nothing grabbed yet and there is something in hand to grab if (self->currentAnimationFrame == 0 && self->pos.y - (self->gameData->entityManager.sprites[BB_GRABBY_HAND].originY << DECIMAL_BITS) - < other->pos.y - 128) + < other->pos.y - 95) { self->paused = false; ghData->grabbed = other; + bb_clearCollisions(other, false); } } From 16be83be101ccc7e3d7e7e12abf64797efb17552 Mon Sep 17 00:00:00 2001 From: DebrisHauler Date: Sat, 18 Jan 2025 23:12:10 -0500 Subject: [PATCH 10/33] Grabby hand missing sprite fixed. (probably) --- main/modes/games/bigbug/entityManager_bigbug.c | 13 +++++++------ main/modes/games/bigbug/entity_bigbug.c | 16 ++++++++++++++-- main/modes/games/bigbug/entity_bigbug.h | 1 + 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/main/modes/games/bigbug/entityManager_bigbug.c b/main/modes/games/bigbug/entityManager_bigbug.c index 59fbc91d1..eaa46ecc2 100644 --- a/main/modes/games/bigbug/entityManager_bigbug.c +++ b/main/modes/games/bigbug/entityManager_bigbug.c @@ -275,11 +275,6 @@ void bb_updateEntities(bb_entityManager_t* entityManager, bb_camera_t* camera) } break; } - case BB_GRABBY_HAND: - { - bb_loadSprite("grab", 3, 1, &entityManager->sprites[BB_GRABBY_HAND]); - break; - } case BB_DOOR: { bb_loadSprite("door", 2, 1, &entityManager->sprites[BB_DOOR]); @@ -400,6 +395,11 @@ void bb_updateEntities(bb_entityManager_t* entityManager, bb_camera_t* camera) { curEntity->updateFarFunction(curEntity); } + else if(curEntity->spriteIndex == BB_GRABBY_HAND && !entityManager->sprites[BB_GRABBY_HAND].allocated) + { + bb_loadSprite("grab", 3, 1, &entityManager->sprites[BB_GRABBY_HAND]); + curEntity->drawFunction = bb_drawGrabbyHand; + } } if (curEntity->collisions != NULL) @@ -1301,7 +1301,7 @@ bb_entity_t* bb_createEntity(bb_entityManager_t* entityManager, bb_animationType = (bb_grabbyHandData_t*)heap_caps_calloc(1, sizeof(bb_grabbyHandData_t), MALLOC_CAP_SPIRAM); bb_setData(entity, ghData, GRABBY_HAND_DATA); - entity->cacheable = true; + entity->cacheable = false; entity->halfWidth = 7 << DECIMAL_BITS; entity->halfHeight = 26 << DECIMAL_BITS; @@ -1319,6 +1319,7 @@ bb_entity_t* bb_createEntity(bb_entityManager_t* entityManager, bb_animationType push(entity->collisions, (void*)collision); entity->updateFunction = &bb_updateGrabbyHand; + entity->updateFarFunction = &bb_updateFarGrabbyHand; entity->drawFunction = &bb_drawGrabbyHand; // sprites loaded just-in-time diff --git a/main/modes/games/bigbug/entity_bigbug.c b/main/modes/games/bigbug/entity_bigbug.c index 409b05378..78fc1cc18 100644 --- a/main/modes/games/bigbug/entity_bigbug.c +++ b/main/modes/games/bigbug/entity_bigbug.c @@ -2202,8 +2202,9 @@ void bb_updateGrabbyHand(bb_entity_t* self) self->pos.y = ghData->rocket->pos.y - 848; // that is 53 << 4 // retreat into the booster - if (self->gameData->entityManager.sprites[BB_GRABBY_HAND].originY > -26) + if (self->gameData->entityManager.sprites[BB_GRABBY_HAND].originY > -26 && (ghData->grabbed == NULL || self->currentAnimationFrame == 2)) { + self->paused = true; self->gameData->entityManager.sprites[BB_GRABBY_HAND].originY -= 2; if (ghData->grabbed != NULL) { @@ -2212,8 +2213,10 @@ void bb_updateGrabbyHand(bb_entity_t* self) = self->pos.y - (self->gameData->entityManager.sprites[BB_GRABBY_HAND].originY << DECIMAL_BITS); } } - else if (ghData->grabbed != NULL) + else if (self->gameData->entityManager.sprites[BB_GRABBY_HAND].originY <= -26 && ghData->grabbed != NULL && self->currentAnimationFrame == 2) { + self->currentAnimationFrame = 0; + self->animationTimer = 0; if (self->gameData->entityManager.playerEntity != NULL && self->gameData->entityManager.playerEntity->dataType == GARBOTNIK_DATA) { @@ -2292,6 +2295,15 @@ void bb_updateGrabbyHand(bb_entity_t* self) } } +void bb_updateFarGrabbyHand(bb_entity_t* self) +{ + if(self->gameData->entityManager.sprites[BB_GRABBY_HAND].allocated) + { + bb_freeSprite(&self->gameData->entityManager.sprites[BB_GRABBY_HAND]); + self->drawFunction = bb_drawNothing; + } +} + void bb_updateDoor(bb_entity_t* self) { if (self->gameData->carFightState == 0) // no fight diff --git a/main/modes/games/bigbug/entity_bigbug.h b/main/modes/games/bigbug/entity_bigbug.h index dfeb7d684..1db55f171 100644 --- a/main/modes/games/bigbug/entity_bigbug.h +++ b/main/modes/games/bigbug/entity_bigbug.h @@ -430,6 +430,7 @@ void bb_updateAttachmentArm(bb_entity_t* self); void bb_updateGameOver(bb_entity_t* self); void bb_updateRadarPing(bb_entity_t* self); void bb_updateGrabbyHand(bb_entity_t* self); +void bb_updateFarGrabbyHand(bb_entity_t* self); void bb_updateDoor(bb_entity_t* self); void bb_updateCarActive(bb_entity_t* self); void bb_updateCarOpen(bb_entity_t* self); From 301c1eb3936ea15265a76c6bdb6388ddc9070b37 Mon Sep 17 00:00:00 2001 From: DebrisHauler Date: Sun, 19 Jan 2025 01:11:02 -0500 Subject: [PATCH 11/33] so many null checks. --- .../modes/games/bigbug/entityManager_bigbug.c | 85 +- main/modes/games/bigbug/entity_bigbug.c | 1202 +++++++++-------- main/modes/games/bigbug/mode_bigbug.c | 125 +- main/modes/games/bigbug/mode_bigbug.h | 1 - 4 files changed, 709 insertions(+), 704 deletions(-) diff --git a/main/modes/games/bigbug/entityManager_bigbug.c b/main/modes/games/bigbug/entityManager_bigbug.c index eaa46ecc2..4bca64f56 100644 --- a/main/modes/games/bigbug/entityManager_bigbug.c +++ b/main/modes/games/bigbug/entityManager_bigbug.c @@ -863,30 +863,32 @@ bb_entity_t* bb_createEntity(bb_entityManager_t* entityManager, bb_animationType bb_entity_t* ovo = bb_createEntity(&entity->gameData->entityManager, NO_ANIMATION, true, OVO_TALK, 1, entity->gameData->camera.camera.pos.x, entity->gameData->camera.camera.pos.y, false, true); - - bb_dialogueData_t* dData = bb_createDialogueData(11, "Ovo"); - - bb_setCharacterLine(dData, 0, "Ovo", "Wow, look at that juicy bug!"); - bb_setCharacterLine(dData, 1, "Ovo", "Ah, who am I kidding?"); - bb_setCharacterLine(dData, 2, "Ovo", "My time in this landfill is limited."); - bb_setCharacterLine(dData, 3, "Ovo", - "So I'm going to break the fourth wall and tell you how to play the freaking game!"); - bb_setCharacterLine( - dData, 4, "Ovo", - "There are a bunch of ways to incapacitate that bug. But let's learn how to use harpoons first."); - bb_setCharacterLine(dData, 5, "Ovo", "Hold your right thumb on the C-Touchpad to aim."); - bb_setCharacterLine(dData, 6, "Ovo", "Hey! Wait until I'm done talking, you doofus."); - bb_setCharacterLine(dData, 7, "Ovo", - "If your touch vector is outside of the purple circle that appears on-screen, harpoons " - "will fire steadily."); - bb_setCharacterLine(dData, 8, "Ovo", "Three hits will flip the bug upside down!"); - bb_setCharacterLine(dData, 9, "Ovo", - "There's a nifty detail where harpoons with upward velocity pass right through terrain."); - bb_setCharacterLine(dData, 10, "Ovo", "Now, let's get that bug!"); - - dData->curString = -1; - dData->endDialogueCB = &bb_afterGarbotnikTutorialTalk; - bb_setData(ovo, dData, DIALOGUE_DATA); + if(ovo != NULL) + { + bb_dialogueData_t* dData = bb_createDialogueData(11, "Ovo"); + + bb_setCharacterLine(dData, 0, "Ovo", "Wow, look at that juicy bug!"); + bb_setCharacterLine(dData, 1, "Ovo", "Ah, who am I kidding?"); + bb_setCharacterLine(dData, 2, "Ovo", "My time in this landfill is limited."); + bb_setCharacterLine(dData, 3, "Ovo", + "So I'm going to break the fourth wall and tell you how to play the freaking game!"); + bb_setCharacterLine( + dData, 4, "Ovo", + "There are a bunch of ways to incapacitate that bug. But let's learn how to use harpoons first."); + bb_setCharacterLine(dData, 5, "Ovo", "Hold your right thumb on the C-Touchpad to aim."); + bb_setCharacterLine(dData, 6, "Ovo", "Hey! Wait until I'm done talking, you doofus."); + bb_setCharacterLine(dData, 7, "Ovo", + "If your touch vector is outside of the purple circle that appears on-screen, harpoons " + "will fire steadily."); + bb_setCharacterLine(dData, 8, "Ovo", "Three hits will flip the bug upside down!"); + bb_setCharacterLine(dData, 9, "Ovo", + "There's a nifty detail where harpoons with upward velocity pass right through terrain."); + bb_setCharacterLine(dData, 10, "Ovo", "Now, let's get that bug!"); + + dData->curString = -1; + dData->endDialogueCB = &bb_afterGarbotnikTutorialTalk; + bb_setData(ovo, dData, DIALOGUE_DATA); + } } entity->spriteIndex = spriteIndex; @@ -1144,16 +1146,20 @@ bb_entity_t* bb_createEntity(bb_entityManager_t* entityManager, bb_animationType { bb_menuData_t* mData = heap_caps_calloc(1, sizeof(bb_menuData_t), MALLOC_CAP_SPIRAM); + bb_ensureEntitySpace(entityManager, 1); mData->cursor = bb_createEntity( entityManager, LOOPING_ANIMATION, false, HARPOON, 3, (entity->pos.x >> DECIMAL_BITS) - 22, 0, false, false); // y position doesn't matter here. It will be handled in the update loop. - bb_clearCollisions(mData->cursor, false); + if(mData->cursor != NULL) + { + bb_clearCollisions(mData->cursor, false); - // This will make it draw pointed right - ((bb_projectileData_t*)mData->cursor->data)->vel = (vec_t){10, 0}; + // This will make it draw pointed right + ((bb_projectileData_t*)mData->cursor->data)->vel = (vec_t){10, 0}; - mData->cursor->updateFunction = NULL; + mData->cursor->updateFunction = NULL; + } bb_setData(entity, mData, MENU_DATA); @@ -1399,20 +1405,23 @@ bb_entity_t* bb_createEntity(bb_entityManager_t* entityManager, bb_animationType entity->gameData->isPaused = true; // set the tutorial flag entity->gameData->tutorialFlags |= 0b10000; - bb_entity_t* ovo = bb_createEntity(&entity->gameData->entityManager, NO_ANIMATION, true, OVO_TALK, 1, + bb_entity_t* ovo = bb_createEntity(entityManager, NO_ANIMATION, true, OVO_TALK, 1, entity->gameData->camera.camera.pos.x, entity->gameData->camera.camera.pos.y, false, true); - bb_dialogueData_t* dData = bb_createDialogueData(3, "Ovo"); + if(ovo != NULL) + { + bb_dialogueData_t* dData = bb_createDialogueData(3, "Ovo"); - bb_setCharacterLine(dData, 0, "Ovo", "I want to eat that donut RIGHT NOW!"); - bb_setCharacterLine(dData, 1, "Ovo", "No. I need to focus. I'll tow it back to the booster."); - bb_setCharacterLine(dData, 2, "Ovo", - "When I eat it at home, it will be the be the impulse for my next great wile."); + bb_setCharacterLine(dData, 0, "Ovo", "I want to eat that donut RIGHT NOW!"); + bb_setCharacterLine(dData, 1, "Ovo", "No. I need to focus. I'll tow it back to the booster."); + bb_setCharacterLine(dData, 2, "Ovo", + "When I eat it at home, it will be the be the impulse for my next great wile."); - dData->curString = -1; - dData->endDialogueCB = &bb_afterGarbotnikTutorialTalk; - bb_setData(ovo, dData, DIALOGUE_DATA); + dData->curString = -1; + dData->endDialogueCB = &bb_afterGarbotnikTutorialTalk; + bb_setData(ovo, dData, DIALOGUE_DATA); + } } entity->halfWidth = 8 << DECIMAL_BITS; @@ -1494,6 +1503,8 @@ bb_entity_t* bb_createEntity(bb_entityManager_t* entityManager, bb_animationType } case BB_501KG: { + //just in time loading + bb_loadSprite("501kg", 1, 1, &entityManager->sprites[BB_501KG]); bb_setData(entity, heap_caps_calloc(1, sizeof(bb_501kgData_t), MALLOC_CAP_SPIRAM), BB_501KG_DATA); entity->updateFunction = &bb_update501kg; entity->drawFunction = &bb_draw501kg; diff --git a/main/modes/games/bigbug/entity_bigbug.c b/main/modes/games/bigbug/entity_bigbug.c index 78fc1cc18..e367d7097 100644 --- a/main/modes/games/bigbug/entity_bigbug.c +++ b/main/modes/games/bigbug/entity_bigbug.c @@ -185,7 +185,8 @@ void bb_updateRocketLanding(bb_entity_t* self) } if (self->pos.y > terrainY - 3000 && rData->flame == NULL) { - rData->flame = bb_createEntity(&(self->gameData->entityManager), LOOPING_ANIMATION, false, FLAME_ANIM, 16, + bb_ensureEntitySpace(&self->gameData->entityManager, 1); + rData->flame = bb_createEntity(&self->gameData->entityManager, LOOPING_ANIMATION, false, FLAME_ANIM, 16, self->pos.x >> DECIMAL_BITS, self->pos.y >> DECIMAL_BITS, false, false); } @@ -336,12 +337,16 @@ void bb_updateRocketLiftoff(bb_entity_t* self) = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, OVO_TALK, 1, self->gameData->camera.camera.pos.x, self->gameData->camera.camera.pos.y, true, true); - bb_dialogueData_t* dData = bb_createDialogueData(1, "Ovo"); - bb_setCharacterLine(dData, 0, "Ovo", "Time to check the loadout!"); - dData->curString = -1; - dData->endDialogueCB = &bb_afterGarbotnikIntro; - bb_setData(ovo, dData, DIALOGUE_DATA); + if(ovo != NULL) + { + bb_dialogueData_t* dData = bb_createDialogueData(1, "Ovo"); + bb_setCharacterLine(dData, 0, "Ovo", "Time to check the loadout!"); + dData->curString = -1; + dData->endDialogueCB = &bb_afterGarbotnikIntro; + bb_setData(ovo, dData, DIALOGUE_DATA); + } + bb_ensureEntitySpace(&self->gameData->entityManager, 1); self->gameData->entityManager.viewEntity = bb_createEntity( &(self->gameData->entityManager), NO_ANIMATION, true, NO_SPRITE_POI, 1, self->gameData->camera.camera.pos.x + 140, self->gameData->camera.camera.pos.y + 120, false, false); @@ -364,6 +369,7 @@ void bb_updateRocketLiftoff(bb_entity_t* self) { self->gameData->endDayChecks = self->gameData->endDayChecks | (1 << 0); // set the pause illusion bit. self->gameData->endDayChecks = self->gameData->endDayChecks | (1 << 1); // set the pango has spoken bit. + bb_ensureEntitySpace(&self->gameData->entityManager, 1); bb_createEntity(&(self->gameData->entityManager), LOOPING_ANIMATION, false, BB_PANGO_AND_FRIENDS, 3, (self->pos.x >> DECIMAL_BITS) - 77, (self->pos.y >> DECIMAL_BITS) - 100, true, false); } @@ -488,26 +494,38 @@ void bb_updateGarbotnikDeploy(bb_entity_t* self) freeWsg(&self->gameData->entityManager.sprites[ROCKET_ANIM].frames[frame]); } + bb_ensureEntitySpace(&self->gameData->entityManager, 1); bb_entity_t* arm = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, ATTACHMENT_ARM, 1, self->pos.x >> DECIMAL_BITS, (self->pos.y >> DECIMAL_BITS) - 33, false, false); - ((bb_rocketData_t*)self->data)->armAngle = 2880; // That is 180 down position. - ((bb_attachmentArmData_t*)arm->data)->rocket = self; + if(arm != NULL) + { + ((bb_rocketData_t*)self->data)->armAngle = 2880; // That is 180 down position. + ((bb_attachmentArmData_t*)arm->data)->rocket = self; + } + bb_ensureEntitySpace(&self->gameData->entityManager, 1); bb_entity_t* grabbyHand = bb_createEntity(&self->gameData->entityManager, LOOPING_ANIMATION, true, BB_GRABBY_HAND, 6, self->pos.x >> DECIMAL_BITS, (self->pos.y >> DECIMAL_BITS) - 53, true, false); - ((bb_grabbyHandData_t*)grabbyHand->data)->rocket = self; + if(grabbyHand != NULL) + { + ((bb_grabbyHandData_t*)grabbyHand->data)->rocket = self; + } self->paused = true; self->gameData->isPaused = true; // deploy garbotnik!!! + bb_ensureEntitySpace(&self->gameData->entityManager, 1); bb_entity_t* garbotnik = bb_createEntity(&(self->gameData->entityManager), NO_ANIMATION, true, GARBOTNIK_FLYING, 1, self->pos.x >> DECIMAL_BITS, (self->pos.y >> DECIMAL_BITS) - 50, true, false); - self->gameData->entityManager.viewEntity = garbotnik; - self->updateFunction = bb_updateHeavyFalling; - bb_startGarbotnikLandingTalk(garbotnik); + if(garbotnik != NULL) + { + self->gameData->entityManager.viewEntity = garbotnik; + self->updateFunction = bb_updateHeavyFalling; + bb_startGarbotnikLandingTalk(garbotnik); + } } } @@ -541,9 +559,9 @@ void bb_updateGarbotnikFlying(bb_entity_t* self) if (gData->r > gData->activationRadius && gData->fire && gData->activeWile != 255) { // Throw a wile! + bb_ensureEntitySpace(&self->gameData->entityManager, 1); bb_entity_t* wile = bb_createEntity(&(self->gameData->entityManager), NO_ANIMATION, true, BB_WILE, 1, self->pos.x >> DECIMAL_BITS, self->pos.y >> DECIMAL_BITS, false, false); - if (wile != NULL) { midiPlayer_t* sfx = soundGetPlayerSfx(); @@ -593,6 +611,7 @@ void bb_updateGarbotnikFlying(bb_entity_t* self) { gData->harpoonCooldown = self->gameData->GarbotnikStat_fireTime; // Create a harpoon + bb_ensureEntitySpace(&self->gameData->entityManager, 1); bb_entity_t* harpoon = bb_createEntity(&(self->gameData->entityManager), LOOPING_ANIMATION, false, HARPOON, 1, self->pos.x >> DECIMAL_BITS, self->pos.y >> DECIMAL_BITS, false, false); if (harpoon != NULL) @@ -1150,29 +1169,31 @@ void bb_updateGarbotnikFlying(bb_entity_t* self) bb_entity_t* ovo = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, OVO_TALK, 1, self->gameData->camera.camera.pos.x, self->gameData->camera.camera.pos.y, false, true); - - bb_dialogueData_t* dData = bb_createDialogueData(9, "Ovo"); - - bb_setCharacterLine(dData, 0, "Ovo", "Glitch my circuits!"); - bb_setCharacterLine(dData, 1, "Ovo", "Low fuel!"); - bb_setCharacterLine(dData, 2, "Ovo", "How could you do this to me?!"); - bb_setCharacterLine(dData, 3, "Ovo", "Extraction is one of the hardest parts of the job."); - bb_setCharacterLine( - dData, 4, "Ovo", - "Plan ahead and clear out any threats around the booster for an uninterrupted extraction."); - bb_setCharacterLine( - dData, 5, "Ovo", - "Keep in mind that my headlamps stimulate the bug eggs. So my facing direction is important."); - bb_setCharacterLine(dData, 6, "Ovo", - "Remember that bugs don't usually dig. So use the garbage to your advantage."); - bb_setCharacterLine(dData, 7, "Ovo", "And bugs far off-screen will stop moving and shooting."); - bb_setCharacterLine( - dData, 8, "Ovo", - "Sometimes you just need to let them wander away to sit safely on the booster for 30 seconds."); - - dData->curString = -1; - dData->endDialogueCB = &bb_afterGarbotnikTutorialTalk; - bb_setData(ovo, dData, DIALOGUE_DATA); + if(ovo != NULL) + { + bb_dialogueData_t* dData = bb_createDialogueData(9, "Ovo"); + + bb_setCharacterLine(dData, 0, "Ovo", "Glitch my circuits!"); + bb_setCharacterLine(dData, 1, "Ovo", "Low fuel!"); + bb_setCharacterLine(dData, 2, "Ovo", "How could you do this to me?!"); + bb_setCharacterLine(dData, 3, "Ovo", "Extraction is one of the hardest parts of the job."); + bb_setCharacterLine( + dData, 4, "Ovo", + "Plan ahead and clear out any threats around the booster for an uninterrupted extraction."); + bb_setCharacterLine( + dData, 5, "Ovo", + "Keep in mind that my headlamps stimulate the bug eggs. So my facing direction is important."); + bb_setCharacterLine(dData, 6, "Ovo", + "Remember that bugs don't usually dig. So use the garbage to your advantage."); + bb_setCharacterLine(dData, 7, "Ovo", "And bugs far off-screen will stop moving and shooting."); + bb_setCharacterLine( + dData, 8, "Ovo", + "Sometimes you just need to let them wander away to sit safely on the booster for 30 seconds."); + + dData->curString = -1; + dData->endDialogueCB = &bb_afterGarbotnikTutorialTalk; + bb_setData(ovo, dData, DIALOGUE_DATA); + } } // exploration song length 5537 // hurry up song length 4833 @@ -1375,10 +1396,10 @@ void bb_updateEggLeaves(bb_entity_t* self) // about before hatching. // create a bug + bb_ensureEntitySpace(&self->gameData->entityManager, 1); bb_entity_t* bug = bb_createEntity(&self->gameData->entityManager, LOOPING_ANIMATION, false, bb_randomInt(8, 13), 1, elData->egg->pos.x >> DECIMAL_BITS, elData->egg->pos.y >> DECIMAL_BITS, false, false); - if (bug != NULL) { // destroy the egg @@ -1813,13 +1834,15 @@ void bb_updateMenu(bb_entity_t* self) bb_rocketData_t* rData = (bb_rocketData_t*)self->gameData->entityManager.boosterEntities[rocketIdx]->data; - + bb_ensureEntitySpace(&self->gameData->entityManager, 1); rData->flame = bb_createEntity( &(self->gameData->entityManager), LOOPING_ANIMATION, false, FLAME_ANIM, 6, self->gameData->entityManager.boosterEntities[rocketIdx]->pos.x >> DECIMAL_BITS, self->gameData->entityManager.boosterEntities[rocketIdx]->pos.y >> DECIMAL_BITS, true, false); - - rData->flame->updateFunction = &bb_updateFlame; + if(rData->flame != NULL) + { + rData->flame->updateFunction = &bb_updateFlame; + } } // create garbotnik's UI @@ -1831,13 +1854,16 @@ void bb_updateMenu(bb_entity_t* self) self->gameData->entityManager.deathDumpster = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, BB_DEATH_DUMPSTER, 1, self->pos.x >> DECIMAL_BITS, (self->pos.y >> DECIMAL_BITS) + 400, false, true); - tData->tracking = self->gameData->entityManager.deathDumpster; - tData->midPointSqDist - = sqMagVec2d(divVec2d((vec_t){(tData->tracking->pos.x >> DECIMAL_BITS) - - (self->gameData->entityManager.viewEntity->pos.x >> DECIMAL_BITS), - (tData->tracking->pos.y >> DECIMAL_BITS) - - (self->gameData->entityManager.viewEntity->pos.y >> DECIMAL_BITS)}, - 2)); + if(self->gameData->entityManager.deathDumpster != NULL) + { + tData->tracking = self->gameData->entityManager.deathDumpster; + tData->midPointSqDist + = sqMagVec2d(divVec2d((vec_t){(tData->tracking->pos.x >> DECIMAL_BITS) + - (self->gameData->entityManager.viewEntity->pos.x >> DECIMAL_BITS), + (tData->tracking->pos.y >> DECIMAL_BITS) + - (self->gameData->entityManager.viewEntity->pos.y >> DECIMAL_BITS)}, + 2)); + } self->gameData->entityManager.viewEntity->updateFunction = &bb_updatePOI; @@ -1886,7 +1912,10 @@ void bb_updateMenu(bb_entity_t* self) bb_entity_t* treadmillDust = bb_createEntity( &self->gameData->entityManager, NO_ANIMATION, true, NO_SPRITE_STAR, 1, (self->pos.x >> DECIMAL_BITS) + 140, (self->pos.y >> DECIMAL_BITS) - 165 + bb_randomInt(-10, 10), false, false); - treadmillDust->updateFunction = &bb_updateMoveLeft; + if(treadmillDust != NULL) + { + treadmillDust->updateFunction = &bb_updateMoveLeft; + } } } @@ -2084,12 +2113,15 @@ void bb_updateAttachmentArm(bb_entity_t* self) self->gameData->entityManager.viewEntity = aData->rocket; aData->rocket->currentAnimationFrame = 0; + bb_ensureEntitySpace(&self->gameData->entityManager, 1); rData->flame = bb_createEntity(&(self->gameData->entityManager), LOOPING_ANIMATION, false, FLAME_ANIM, 6, aData->rocket->pos.x >> DECIMAL_BITS, aData->rocket->pos.y >> DECIMAL_BITS, true, false); - - rData->flame->updateFunction = &bb_updateFlame; - aData->rocket->updateFunction = &bb_updateRocketLiftoff; + if(rData->flame != NULL) + { + rData->flame->updateFunction = &bb_updateFlame; + aData->rocket->updateFunction = &bb_updateRocketLiftoff; + } bb_destroyEntity(self, false, true); } @@ -2254,21 +2286,23 @@ void bb_updateGrabbyHand(bb_entity_t* self) bb_entity_t* ovo = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, OVO_TALK, 1, self->gameData->camera.camera.pos.x, self->gameData->camera.camera.pos.y, false, true); - - bb_dialogueData_t* dData = bb_createDialogueData(5, "Ovo"); - - bb_setCharacterLine(dData, 0, "Ovo", "One down, nine more to go!"); - bb_setCharacterLine(dData, 1, "Ovo", "For every 10 bugs collected, the booster will emit a mega ping!"); - bb_setCharacterLine(dData, 2, "Ovo", - "When the mega ping is done, you can tune into some of the more important results."); - bb_setCharacterLine(dData, 3, "Ovo", - "Oh, you can pulse your own radar anytime with the start button. It takes some time to " - "interpret the pings."); - bb_setCharacterLine(dData, 4, "Ovo", "So keep exploring while you wait."); - - dData->curString = -1; - dData->endDialogueCB = &bb_afterGarbotnikTutorialTalk; - bb_setData(ovo, dData, DIALOGUE_DATA); + if(ovo!=NULL) + { + bb_dialogueData_t* dData = bb_createDialogueData(5, "Ovo"); + + bb_setCharacterLine(dData, 0, "Ovo", "One down, nine more to go!"); + bb_setCharacterLine(dData, 1, "Ovo", "For every 10 bugs collected, the booster will emit a mega ping!"); + bb_setCharacterLine(dData, 2, "Ovo", + "When the mega ping is done, you can tune into some of the more important results."); + bb_setCharacterLine(dData, 3, "Ovo", + "Oh, you can pulse your own radar anytime with the start button. It takes some time to " + "interpret the pings."); + bb_setCharacterLine(dData, 4, "Ovo", "So keep exploring while you wait."); + + dData->curString = -1; + dData->endDialogueCB = &bb_afterGarbotnikTutorialTalk; + bb_setData(ovo, dData, DIALOGUE_DATA); + } } rData->numBugs++; @@ -2281,9 +2315,12 @@ void bb_updateGrabbyHand(bb_entity_t* self) bb_entity_t* radarPing = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, BB_RADAR_PING, 1, self->pos.x >> DECIMAL_BITS, self->pos.y >> DECIMAL_BITS, true, false); - bb_radarPingData_t* rpData = (bb_radarPingData_t*)radarPing->data; - rpData->color = c541; - rpData->executeAfterPing = &bb_upgradeRadar; + if(radarPing != NULL) + { + bb_radarPingData_t* rpData = (bb_radarPingData_t*)radarPing->data; + rpData->color = c541; + rpData->executeAfterPing = &bb_upgradeRadar; + } } self->currentAnimationFrame = 0; @@ -2361,6 +2398,7 @@ void bb_updateCarOpen(bb_entity_t* self) case BB_DONUT: { // spawn a donut as a reward for completing the fight + bb_ensureEntitySpace(&self->gameData->entityManager, 1); bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, BB_DONUT, 1, (self->pos.x >> DECIMAL_BITS) + 5, (self->pos.y >> DECIMAL_BITS), true, false); break; @@ -2368,6 +2406,7 @@ void bb_updateCarOpen(bb_entity_t* self) default: // BB_SWADGE { // spawn a swadge as a reward for completing the fight + bb_ensureEntitySpace(&self->gameData->entityManager, 1); bb_createEntity(&self->gameData->entityManager, LOOPING_ANIMATION, false, BB_SWADGE, 9, (self->pos.x >> DECIMAL_BITS) + 40, (self->pos.y >> DECIMAL_BITS) - 32, true, false); break; @@ -2450,125 +2489,127 @@ void bb_updatePangoAndFriends(bb_entity_t* self) bb_entity_t* ovo = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, OVO_TALK, 1, self->gameData->camera.camera.pos.x, self->gameData->camera.camera.pos.y, false, true); - - if (self->gameData->day == 0) - { - bb_dialogueData_t* dData = bb_createDialogueData(18, "Pixel"); - - // longest possible string " " - // bb_setCharacterLine(dData, 0, "A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A - // A A A A A A A A A A A A A A A A A A A A A A A"); - bb_setCharacterLine(dData, 0, "Pixel", "huff... huff... we actually caught up to it!"); - bb_setCharacterLine(dData, 1, "Pixel", - "ugfhh, my hair is starting to hurt. Can you make this quick, Pango?"); - bb_setCharacterLine(dData, 2, "Pixel", "Hey, it looks like it's Garbotnik after all!"); - bb_setCharacterLine(dData, 3, "Po", "That's a cool ride, bro."); - bb_setCharacterLine(dData, 4, "Ovo", "What could you idiots possibly want?"); - bb_setCharacterLine(dData, 5, "Pango", - "We were fascinated with your shiny metal tube falling from space this morning."); - bb_setCharacterLine(dData, 6, "Po", "Yeah, we were wondering if you could give us a ride."); - bb_setCharacterLine(dData, 7, "Ovo", "I'm not a taxi service."); - bb_setCharacterLine(dData, 8, "Pixel", "We'll pay you in swadges."); - bb_setCharacterLine(dData, 9, "Po", "What are you doing at the landfill, anyway?"); - bb_setCharacterLine(dData, 10, "Ovo", "None of your business!"); - bb_setCharacterLine(dData, 11, "Ovo", "Get off my freaking back!"); - bb_setCharacterLine(dData, 12, "Ovo", "I have a dive summary to review."); - bb_setCharacterLine(dData, 13, "Pixel", "What's a dive summary?"); - bb_setCharacterLine(dData, 14, "Ovo", "It tells me how many bugs I annihilated."); - bb_setCharacterLine(dData, 15, "Pixel", "Hmm... That sounds like he's up to no good."); - bb_setCharacterLine(dData, 16, "Ovo", "Bug off!"); - bb_setCharacterLine(dData, 17, "Ovo", "Filthy peasants."); - - dData->curString = -1; - dData->endDialogueCB = &bb_afterLiftoffInteraction; - bb_setData(ovo, dData, DIALOGUE_DATA); - } - else if (self->gameData->day == 1) - { - bb_dialogueData_t* dData = bb_createDialogueData(10, "Pixel"); - - bb_setCharacterLine(dData, 0, "Pixel", - "Hey, Mr. Garbotnik! I really love the color of your umbrella so much!"); - bb_setCharacterLine(dData, 1, "Ovo", "Glitch my circuits!"); - bb_setCharacterLine(dData, 2, "Ovo", "Now I have to find a new one!"); - bb_setCharacterLine(dData, 3, "Po", "What's your problem, dude? Pink is a great color!"); - bb_setCharacterLine(dData, 4, "Pango", "Nobody spends their Thursdays at the dump for leisure."); - bb_setCharacterLine(dData, 5, "Pango", "We're going to find out what you're up to here."); - bb_setCharacterLine(dData, 6, "Ovo", "Oh, bug off!"); - bb_setCharacterLine(dData, 7, "Ovo", "Don't you have anything better to do?"); - bb_setCharacterLine(dData, 8, "Ovo", "Lousy ingrates."); - bb_setCharacterLine(dData, 9, "Pixel", "That man doesn't know the meaning of friendship."); - - dData->curString = -1; - dData->endDialogueCB = &bb_afterLiftoffInteraction; - bb_setData(ovo, dData, DIALOGUE_DATA); - } - else if (self->gameData->day == 2) - { - bb_dialogueData_t* dData = bb_createDialogueData(8, "Pango"); - - bb_setCharacterLine(dData, 0, "Pango", "Why do you have better shading than us?"); - bb_setCharacterLine(dData, 1, "Ovo", "Because I'm the main character."); - bb_setCharacterLine(dData, 2, "Pango", "But MAGFast is literally named after me!"); - bb_setCharacterLine(dData, 3, "Ovo", "You fool! It's called mag FEST because of PEST like you."); - bb_setCharacterLine(dData, 4, "Po", "That's so metal."); - bb_setCharacterLine(dData, 5, "Ovo", - "POOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO" - "OOOOOOOOOOOOOOOOOOOO"); - bb_setCharacterLine(dData, 6, "Ovo", - "OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO" - "OOOOOOOOOOOOOOOOOOOOOOO!"); - bb_setCharacterLine(dData, 7, "Pixel", "Hey, don't yell at my friend like that!"); - - dData->curString = -1; - dData->endDialogueCB = &bb_afterLiftoffInteraction; - bb_setData(ovo, dData, DIALOGUE_DATA); - } - else if (self->gameData->day == 3) - { - bb_dialogueData_t* dData = bb_createDialogueData(8, "Pango"); - - bb_setCharacterLine(dData, 0, "Pango", "GARBOTNIK! Are you feeling well?"); - bb_setCharacterLine(dData, 1, "Ovo", "Huh?"); - bb_setCharacterLine(dData, 2, "Pango", "Looks like you've got a case of the T-rash!"); - bb_setCharacterLine(dData, 3, "Ovo", "Aaaaaargh! Darn it! Darn it! DARN IT! DARN IT!"); - bb_setCharacterLine(dData, 4, "Ovo", "GLITCH MY CIRCUITS!"); - bb_setCharacterLine(dData, 5, "Pixel", "Freaking got him, Pango!"); - bb_setCharacterLine(dData, 6, "Ovo", "T-raaaaash goes in the T-raaaaaash can."); - bb_setCharacterLine(dData, 7, "Po", "Huh?"); - - dData->curString = -1; - dData->endDialogueCB = &bb_afterLiftoffInteraction; - bb_setData(ovo, dData, DIALOGUE_DATA); - } - else if (self->gameData->day == 99) + if(ovo != NULL) { - bb_dialogueData_t* dData = bb_createDialogueData(3, "Pango"); - - bb_setCharacterLine(dData, 0, "Pango", "You're completely unhinged."); - bb_setCharacterLine(dData, 1, "Ovo", "Aaaaaaaand..."); - bb_setCharacterLine(dData, 2, "Ovo", "You're not?"); - - dData->curString = -1; - dData->endDialogueCB = &bb_afterLiftoffInteraction; - bb_setData(ovo, dData, DIALOGUE_DATA); - } - else - { - bb_dialogueData_t* dData = bb_createDialogueData(3, "Po"); + if (self->gameData->day == 0) + { + bb_dialogueData_t* dData = bb_createDialogueData(18, "Pixel"); + + // longest possible string " " + // bb_setCharacterLine(dData, 0, "A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A + // A A A A A A A A A A A A A A A A A A A A A A A"); + bb_setCharacterLine(dData, 0, "Pixel", "huff... huff... we actually caught up to it!"); + bb_setCharacterLine(dData, 1, "Pixel", + "ugfhh, my hair is starting to hurt. Can you make this quick, Pango?"); + bb_setCharacterLine(dData, 2, "Pixel", "Hey, it looks like it's Garbotnik after all!"); + bb_setCharacterLine(dData, 3, "Po", "That's a cool ride, bro."); + bb_setCharacterLine(dData, 4, "Ovo", "What could you idiots possibly want?"); + bb_setCharacterLine(dData, 5, "Pango", + "We were fascinated with your shiny metal tube falling from space this morning."); + bb_setCharacterLine(dData, 6, "Po", "Yeah, we were wondering if you could give us a ride."); + bb_setCharacterLine(dData, 7, "Ovo", "I'm not a taxi service."); + bb_setCharacterLine(dData, 8, "Pixel", "We'll pay you in swadges."); + bb_setCharacterLine(dData, 9, "Po", "What are you doing at the landfill, anyway?"); + bb_setCharacterLine(dData, 10, "Ovo", "None of your business!"); + bb_setCharacterLine(dData, 11, "Ovo", "Get off my freaking back!"); + bb_setCharacterLine(dData, 12, "Ovo", "I have a dive summary to review."); + bb_setCharacterLine(dData, 13, "Pixel", "What's a dive summary?"); + bb_setCharacterLine(dData, 14, "Ovo", "It tells me how many bugs I annihilated."); + bb_setCharacterLine(dData, 15, "Pixel", "Hmm... That sounds like he's up to no good."); + bb_setCharacterLine(dData, 16, "Ovo", "Bug off!"); + bb_setCharacterLine(dData, 17, "Ovo", "Filthy peasants."); + + dData->curString = -1; + dData->endDialogueCB = &bb_afterLiftoffInteraction; + bb_setData(ovo, dData, DIALOGUE_DATA); + } + else if (self->gameData->day == 1) + { + bb_dialogueData_t* dData = bb_createDialogueData(10, "Pixel"); + + bb_setCharacterLine(dData, 0, "Pixel", + "Hey, Mr. Garbotnik! I really love the color of your umbrella so much!"); + bb_setCharacterLine(dData, 1, "Ovo", "Glitch my circuits!"); + bb_setCharacterLine(dData, 2, "Ovo", "Now I have to find a new one!"); + bb_setCharacterLine(dData, 3, "Po", "What's your problem, dude? Pink is a great color!"); + bb_setCharacterLine(dData, 4, "Pango", "Nobody spends their Thursdays at the dump for leisure."); + bb_setCharacterLine(dData, 5, "Pango", "We're going to find out what you're up to here."); + bb_setCharacterLine(dData, 6, "Ovo", "Oh, bug off!"); + bb_setCharacterLine(dData, 7, "Ovo", "Don't you have anything better to do?"); + bb_setCharacterLine(dData, 8, "Ovo", "Lousy ingrates."); + bb_setCharacterLine(dData, 9, "Pixel", "That man doesn't know the meaning of friendship."); + + dData->curString = -1; + dData->endDialogueCB = &bb_afterLiftoffInteraction; + bb_setData(ovo, dData, DIALOGUE_DATA); + } + else if (self->gameData->day == 2) + { + bb_dialogueData_t* dData = bb_createDialogueData(8, "Pango"); + + bb_setCharacterLine(dData, 0, "Pango", "Why do you have better shading than us?"); + bb_setCharacterLine(dData, 1, "Ovo", "Because I'm the main character."); + bb_setCharacterLine(dData, 2, "Pango", "But MAGFast is literally named after me!"); + bb_setCharacterLine(dData, 3, "Ovo", "You fool! It's called mag FEST because of PEST like you."); + bb_setCharacterLine(dData, 4, "Po", "That's so metal."); + bb_setCharacterLine(dData, 5, "Ovo", + "POOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO" + "OOOOOOOOOOOOOOOOOOOO"); + bb_setCharacterLine(dData, 6, "Ovo", + "OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO" + "OOOOOOOOOOOOOOOOOOOOOOO!"); + bb_setCharacterLine(dData, 7, "Pixel", "Hey, don't yell at my friend like that!"); + + dData->curString = -1; + dData->endDialogueCB = &bb_afterLiftoffInteraction; + bb_setData(ovo, dData, DIALOGUE_DATA); + } + else if (self->gameData->day == 3) + { + bb_dialogueData_t* dData = bb_createDialogueData(8, "Pango"); + + bb_setCharacterLine(dData, 0, "Pango", "GARBOTNIK! Are you feeling well?"); + bb_setCharacterLine(dData, 1, "Ovo", "Huh?"); + bb_setCharacterLine(dData, 2, "Pango", "Looks like you've got a case of the T-rash!"); + bb_setCharacterLine(dData, 3, "Ovo", "Aaaaaargh! Darn it! Darn it! DARN IT! DARN IT!"); + bb_setCharacterLine(dData, 4, "Ovo", "GLITCH MY CIRCUITS!"); + bb_setCharacterLine(dData, 5, "Pixel", "Freaking got him, Pango!"); + bb_setCharacterLine(dData, 6, "Ovo", "T-raaaaash goes in the T-raaaaaash can."); + bb_setCharacterLine(dData, 7, "Po", "Huh?"); + + dData->curString = -1; + dData->endDialogueCB = &bb_afterLiftoffInteraction; + bb_setData(ovo, dData, DIALOGUE_DATA); + } + else if (self->gameData->day == 99) + { + bb_dialogueData_t* dData = bb_createDialogueData(3, "Pango"); - bb_setCharacterLine(dData, 0, "Po", "Still looking?"); - char pangosLine[34]; - snprintf(pangosLine, sizeof(pangosLine), "It's been %d days at the dump.", self->gameData->day + 1); - bb_setCharacterLine(dData, 1, "Pango", pangosLine); - bb_setCharacterLine(dData, 2, "Pixel", "I guess that means you're having no luck."); - bb_setCharacterLine(dData, 2, "Ovo", "Glitch my circuits!"); - bb_setCharacterLine(dData, 2, "Ovo", "I. know."); - bb_setCharacterLine(dData, 2, "Ovo", "Bug off!"); + bb_setCharacterLine(dData, 0, "Pango", "You're completely unhinged."); + bb_setCharacterLine(dData, 1, "Ovo", "Aaaaaaaand..."); + bb_setCharacterLine(dData, 2, "Ovo", "You're not?"); - dData->curString = -1; - dData->endDialogueCB = &bb_afterLiftoffInteraction; - bb_setData(ovo, dData, DIALOGUE_DATA); + dData->curString = -1; + dData->endDialogueCB = &bb_afterLiftoffInteraction; + bb_setData(ovo, dData, DIALOGUE_DATA); + } + else + { + bb_dialogueData_t* dData = bb_createDialogueData(3, "Po"); + + bb_setCharacterLine(dData, 0, "Po", "Still looking?"); + char pangosLine[34]; + snprintf(pangosLine, sizeof(pangosLine), "It's been %d days at the dump.", self->gameData->day + 1); + bb_setCharacterLine(dData, 1, "Pango", pangosLine); + bb_setCharacterLine(dData, 2, "Pixel", "I guess that means you're having no luck."); + bb_setCharacterLine(dData, 2, "Ovo", "Glitch my circuits!"); + bb_setCharacterLine(dData, 2, "Ovo", "I. know."); + bb_setCharacterLine(dData, 2, "Ovo", "Bug off!"); + + dData->curString = -1; + dData->endDialogueCB = &bb_afterLiftoffInteraction; + bb_setData(ovo, dData, DIALOGUE_DATA); + } } self->updateFunction = NULL; } @@ -2676,12 +2717,15 @@ void bb_update501kg(bb_entity_t* self) if (fData->vel.y <= 0) { // detonate - freeWsg(&self->gameData->entityManager.sprites[BB_501KG].frames[0]); + bb_freeSprite(&self->gameData->entityManager.sprites[BB_501KG]); bb_entity_t* explosion = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, BB_EXPLOSION, 1, self->pos.x >> DECIMAL_BITS, self->pos.y >> DECIMAL_BITS, false, false); - bb_explosionData_t* eData = (bb_explosionData_t*)explosion->data; - eData->radius = 180; + if(explosion != NULL) + { + bb_explosionData_t* eData = (bb_explosionData_t*)explosion->data; + eData->radius = 180; + } bb_destroyEntity(self, false, true); } @@ -3073,32 +3117,42 @@ void bb_updateQuickplay(bb_entity_t* self) if (rocketIdx >= 1) { bb_rocketData_t* rData = (bb_rocketData_t*)self->gameData->entityManager.boosterEntities[rocketIdx]->data; - + bb_ensureEntitySpace(&self->gameData->entityManager, 1); rData->flame = bb_createEntity( &(self->gameData->entityManager), LOOPING_ANIMATION, false, FLAME_ANIM, 6, self->gameData->entityManager.boosterEntities[rocketIdx]->pos.x >> DECIMAL_BITS, self->gameData->entityManager.boosterEntities[rocketIdx]->pos.y >> DECIMAL_BITS, true, false); - - rData->flame->updateFunction = &bb_updateFlame; + if(rData->flame != NULL) + { + rData->flame->updateFunction = &bb_updateFlame; + } } else // rocketIdx == 0 { self->gameData->entityManager.activeBooster = self->gameData->entityManager.boosterEntities[rocketIdx]; - ((bb_rocketData_t*)self->gameData->entityManager.activeBooster->data)->numDonuts = 20; self->gameData->entityManager.activeBooster->currentAnimationFrame = 40; self->gameData->entityManager.activeBooster->pos.y = 50; self->gameData->entityManager.activeBooster->updateFunction = bb_updateHeavyFalling; + + bb_ensureEntitySpace(&self->gameData->entityManager, 1); bb_entity_t* arm = bb_createEntity( &self->gameData->entityManager, NO_ANIMATION, true, ATTACHMENT_ARM, 1, self->gameData->entityManager.activeBooster->pos.x >> DECIMAL_BITS, (self->gameData->entityManager.activeBooster->pos.y >> DECIMAL_BITS) - 33, true, false); - ((bb_attachmentArmData_t*)arm->data)->rocket = self->gameData->entityManager.activeBooster; + if(arm != NULL) + { + ((bb_attachmentArmData_t*)arm->data)->rocket = self->gameData->entityManager.activeBooster; + } + bb_ensureEntitySpace(&self->gameData->entityManager, 1); bb_entity_t* grabbyHand = bb_createEntity( &self->gameData->entityManager, LOOPING_ANIMATION, true, BB_GRABBY_HAND, 6, self->gameData->entityManager.activeBooster->pos.x >> DECIMAL_BITS, (self->gameData->entityManager.activeBooster->pos.y >> DECIMAL_BITS) - 53, true, false); - ((bb_grabbyHandData_t*)grabbyHand->data)->rocket = self->gameData->entityManager.activeBooster; + if(grabbyHand != NULL) + { + ((bb_grabbyHandData_t*)grabbyHand->data)->rocket = self->gameData->entityManager.activeBooster; + } } } @@ -3110,15 +3164,18 @@ void bb_updateQuickplay(bb_entity_t* self) // create garbotnik's UI bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, BB_GARBOTNIK_UI, 1, 0, 0, false, true); + bb_ensureEntitySpace(&self->gameData->entityManager, 1); self->gameData->entityManager.viewEntity = bb_createEntity(&(self->gameData->entityManager), NO_ANIMATION, true, GARBOTNIK_FLYING, 1, self->gameData->entityManager.activeBooster->pos.x >> DECIMAL_BITS, (self->gameData->entityManager.activeBooster->pos.y >> DECIMAL_BITS) - 90, true, false); + if(self->gameData->entityManager.viewEntity != NULL) + { + self->gameData->entityManager.playerEntity = self->gameData->entityManager.viewEntity; + } bb_loadWsgs(&self->gameData->tilemap); - self->gameData->entityManager.playerEntity = self->gameData->entityManager.viewEntity; - int8_t oldBoosterYs[3] = {-1, -1, -1}; bb_generateWorld(&(self->gameData->tilemap), oldBoosterYs); @@ -4181,7 +4238,7 @@ void bb_drawSpaceLaser(bb_entityManager_t* entityManager, rectangle_t* camera, b bb_entity_t* bump = bb_createEntity(entityManager, ONESHOT_ANIMATION, false, BUMP_ANIM, 1, (self->pos.x >> DECIMAL_BITS) - 12 + foo % 25, (self->pos.y + self->halfHeight) >> DECIMAL_BITS, true, false); - if (foo >= 25) + if (foo >= 45 && bump != NULL) { bump->drawFunction = bb_drawHitEffect; } @@ -4295,26 +4352,28 @@ void bb_onCollisionHarpoon(bb_entity_t* self, bb_entity_t* other, bb_hitInfo_t* bb_entity_t* ovo = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, OVO_TALK, 1, self->gameData->camera.camera.pos.x, self->gameData->camera.camera.pos.y, false, true); - - bb_dialogueData_t* dData = bb_createDialogueData(7, "Ovo"); - - bb_setCharacterLine(dData, 0, "Ovo", "ARLIGHT! You got it!!!"); - bb_setCharacterLine(dData, 1, "Ovo", "Ahem... I mean, not bad."); - bb_setCharacterLine(dData, 2, "Ovo", "In fact, I'm the one who got it."); - bb_setCharacterLine(dData, 3, "Ovo", - "Press the 'A' button near a bug to attach a cable and tow it back to the " - "booster to get credit."); - bb_setCharacterLine( - dData, 4, "Ovo", - "Careful with that tow cable, because a right-side-up bug will drag me around."); - bb_setCharacterLine(dData, 5, "Ovo", - "Did you know bugs can carry over 100 times their body weight?"); - bb_setCharacterLine(dData, 6, "Ovo", - "They're pretty strong, but I wonder if I could pry them off the ceiling."); - - dData->curString = -1; - dData->endDialogueCB = &bb_afterGarbotnikTutorialTalk; - bb_setData(ovo, dData, DIALOGUE_DATA); + if(ovo != NULL) + { + bb_dialogueData_t* dData = bb_createDialogueData(7, "Ovo"); + + bb_setCharacterLine(dData, 0, "Ovo", "ARLIGHT! You got it!!!"); + bb_setCharacterLine(dData, 1, "Ovo", "Ahem... I mean, not bad."); + bb_setCharacterLine(dData, 2, "Ovo", "In fact, I'm the one who got it."); + bb_setCharacterLine(dData, 3, "Ovo", + "Press the 'A' button near a bug to attach a cable and tow it back to the " + "booster to get credit."); + bb_setCharacterLine( + dData, 4, "Ovo", + "Careful with that tow cable, because a right-side-up bug will drag me around."); + bb_setCharacterLine(dData, 5, "Ovo", + "Did you know bugs can carry over 100 times their body weight?"); + bb_setCharacterLine(dData, 6, "Ovo", + "They're pretty strong, but I wonder if I could pry them off the ceiling."); + + dData->curString = -1; + dData->endDialogueCB = &bb_afterGarbotnikTutorialTalk; + bb_setData(ovo, dData, DIALOGUE_DATA); + } } } else @@ -4435,16 +4494,18 @@ void bb_onCollisionRocketGarbotnik(bb_entity_t* self, bb_entity_t* other, bb_hit { rData->numDonuts--; // Get the donut back out of the busted booster + bb_ensureEntitySpace(&self->gameData->entityManager, 1); bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, BB_DONUT, 1, - (self->pos.x >> DECIMAL_BITS) + 5, (self->pos.y >> DECIMAL_BITS), true, false); + (self->pos.x >> DECIMAL_BITS) + 5, (self->pos.y >> DECIMAL_BITS) - 40, true, false); } if ((rData->numBugs % 10 != 0) && bb_randomInt(0, 60) == 60) { rData->numBugs--; // Get the bug back out of the busted booster + bb_ensureEntitySpace(&self->gameData->entityManager, 1); bb_entity_t* bug = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, bb_randomInt(8, 13), 1, - (self->pos.x >> DECIMAL_BITS) + 5, (self->pos.y >> DECIMAL_BITS), true, false); + (self->pos.x >> DECIMAL_BITS) + 5, (self->pos.y >> DECIMAL_BITS) - 40, true, false); bb_bugDeath(bug, NULL); ((bb_physicsData_t*)bug->data)->vel = (vec_t){bb_randomInt(-100, 100), bb_randomInt(-100, 0)}; } @@ -4802,28 +4863,30 @@ void bb_onCollisionBrickTutorial(bb_entity_t* self, bb_entity_t* other, bb_hitIn bb_entity_t* ovo = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, OVO_TALK, 1, self->gameData->camera.camera.pos.x, self->gameData->camera.camera.pos.y, false, true); + if(ovo != NULL) + { + bb_dialogueData_t* dData = bb_createDialogueData(12, "Ovo"); - bb_dialogueData_t* dData = bb_createDialogueData(12, "Ovo"); - - bb_setCharacterLine(dData, 0, "Ovo", "\"---Incoming Transmission from Mr. Dev---\""); - bb_setCharacterLine(dData, 1, "Ovo", "What in the heck?"); - bb_setCharacterLine(dData, 2, "Ovo", "Dang radio's acting up again!"); - bb_setCharacterLine(dData, 3, "Ovo", "\"The bricks can be destroyed by dealing 100 damage.\""); - bb_setCharacterLine(dData, 4, "Ovo", "\"Bring Ovo Garbotnik to a complete stop in a tight gap.\""); - bb_setCharacterLine(dData, 5, "Ovo", - "\"Then move briefly toward the brick to initiate a ping-ponging movement pattern.\""); - bb_setCharacterLine( - dData, 6, "Ovo", - "\"Same as the other garbage densities, bricks will crumble if they are totally disconnected.\""); - bb_setCharacterLine(dData, 7, "Ovo", "\"Or they can be smashed easily with heavy falling objects.\""); - bb_setCharacterLine(dData, 8, "Ovo", "\"Big Bug's early-game design is all about using your fuel to get upgrades.\""); - bb_setCharacterLine(dData, 9, "Ovo", "\"So be sure to strategize how to use your time!\""); - bb_setCharacterLine(dData, 10, "Ovo", "\"The late-game design has a shift in focus away from fuel.\""); - bb_setCharacterLine(dData, 11, "Ovo", "\"You'll just have to see it for yourself!\""); + bb_setCharacterLine(dData, 0, "Ovo", "\"---Incoming Transmission from Mr. Dev---\""); + bb_setCharacterLine(dData, 1, "Ovo", "What in the heck?"); + bb_setCharacterLine(dData, 2, "Ovo", "Dang radio's acting up again!"); + bb_setCharacterLine(dData, 3, "Ovo", "\"The bricks can be destroyed by dealing 100 damage.\""); + bb_setCharacterLine(dData, 4, "Ovo", "\"Bring Ovo Garbotnik to a complete stop in a tight gap.\""); + bb_setCharacterLine(dData, 5, "Ovo", + "\"Then move briefly toward the brick to initiate a ping-ponging movement pattern.\""); + bb_setCharacterLine( + dData, 6, "Ovo", + "\"Same as the other garbage densities, bricks will crumble if they are totally disconnected.\""); + bb_setCharacterLine(dData, 7, "Ovo", "\"Or they can be smashed easily with heavy falling objects.\""); + bb_setCharacterLine(dData, 8, "Ovo", "\"Big Bug's early-game design is all about using your fuel to get upgrades.\""); + bb_setCharacterLine(dData, 9, "Ovo", "\"So be sure to strategize how to use your time!\""); + bb_setCharacterLine(dData, 10, "Ovo", "\"The late-game design has a shift in focus away from fuel.\""); + bb_setCharacterLine(dData, 11, "Ovo", "\"You'll just have to see it for yourself!\""); - dData->curString = -1; - dData->endDialogueCB = &bb_afterGarbotnikTutorialTalk; - bb_setData(ovo, dData, DIALOGUE_DATA); + dData->curString = -1; + dData->endDialogueCB = &bb_afterGarbotnikTutorialTalk; + bb_setData(ovo, dData, DIALOGUE_DATA); + } } bb_destroyEntity(self, false, true); } @@ -4863,51 +4926,53 @@ void bb_startGarbotnikIntro(bb_entity_t* self) bb_entity_t* ovo = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, OVO_TALK, 1, self->gameData->camera.camera.pos.x, self->gameData->camera.camera.pos.y, false, true); + if(ovo != NULL) + { + bb_dialogueData_t* dData = bb_createDialogueData(24, "Ovo"); - bb_dialogueData_t* dData = bb_createDialogueData(24, "Ovo"); - - // longest possible string " " - // bb_setCharacterLine(dData, 0, "A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A - // A A A A A A A A A A A A A A A A A A A"); - bb_setCharacterLine(dData, 0, "Ovo", "Holy bug farts!"); // - bb_setCharacterLine( - dData, 1, "Ovo", - "After I sold the chilidog car fresheners to MAGFest, Garbotnik Industries' stock went up by 6,969%!"); - bb_setCharacterLine(dData, 2, "Ovo", - "I'm going to use my time machine to steal the next big-selling trinket from the future now."); - bb_setCharacterLine(dData, 3, "Ovo", "That will floor all my stakeholders and make me UNDEFINED money!"); - bb_setCharacterLine( - dData, 4, "Ovo", - "With that kind of cash, I can recruit 200 professional bassoon players to the MAGFest Community Orchestra."); - bb_setCharacterLine(dData, 5, "Ovo", "I'm so hyped to turn on my time machine for the first time!"); - bb_setCharacterLine(dData, 6, "Ovo", "Everything's in order."); - bb_setCharacterLine(dData, 7, "Ovo", "Even Pango can't stop me!"); - bb_setCharacterLine(dData, 8, "Ovo", "I just have to attach the chaos orb right here."); - bb_setCharacterLine(dData, 9, "Ovo", "Where did I put that orb?"); - bb_setCharacterLine(dData, 10, "Ovo", "hmmm..."); - bb_setCharacterLine(dData, 11, "Ovo", "What about in the freezer?"); - bb_setCharacterLine(dData, 12, "Ovo", "I've checked every inch of the death dumpster."); - bb_setCharacterLine(dData, 13, "Ovo", "Glitch my circuits!"); - bb_setCharacterLine(dData, 14, "Ovo", "It must have gone out with the trash last Wednesday."); - bb_setCharacterLine(dData, 15, "Ovo", "Can I get an F in the chat?"); - bb_setCharacterLine(dData, 16, "Ovo", "..."); - bb_setCharacterLine(dData, 17, "Ovo", "The chaos orb is three times denser than a black hole."); - bb_setCharacterLine(dData, 18, "Ovo", - "Well if Garbotnik Sanitation Industries took it to the landfill, then it is definitely at the " - "VERY BOTTOM of the dump."); - bb_setCharacterLine(dData, 19, "Ovo", "Not a problem."); - bb_setCharacterLine(dData, 20, "Ovo", "We have the technology to retrieve it."); - bb_setCharacterLine( - dData, 21, "Ovo", - "Safety first. Activate the cloning machine in case I should perish on that nuclear wasteland."); - bb_setCharacterLine(dData, 22, "Ovo", "fine."); - bb_setCharacterLine(dData, 23, "Ovo", "Stupid safety rules. YOLO!"); - - dData->curString = -1; - - dData->endDialogueCB = &bb_afterGarbotnikIntro; - - bb_setData(ovo, dData, DIALOGUE_DATA); + // longest possible string " " + // bb_setCharacterLine(dData, 0, "A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A + // A A A A A A A A A A A A A A A A A A A"); + bb_setCharacterLine(dData, 0, "Ovo", "Holy bug farts!"); // + bb_setCharacterLine( + dData, 1, "Ovo", + "After I sold the chilidog car fresheners to MAGFest, Garbotnik Industries' stock went up by 6,969%!"); + bb_setCharacterLine(dData, 2, "Ovo", + "I'm going to use my time machine to steal the next big-selling trinket from the future now."); + bb_setCharacterLine(dData, 3, "Ovo", "That will floor all my stakeholders and make me UNDEFINED money!"); + bb_setCharacterLine( + dData, 4, "Ovo", + "With that kind of cash, I can recruit 200 professional bassoon players to the MAGFest Community Orchestra."); + bb_setCharacterLine(dData, 5, "Ovo", "I'm so hyped to turn on my time machine for the first time!"); + bb_setCharacterLine(dData, 6, "Ovo", "Everything's in order."); + bb_setCharacterLine(dData, 7, "Ovo", "Even Pango can't stop me!"); + bb_setCharacterLine(dData, 8, "Ovo", "I just have to attach the chaos orb right here."); + bb_setCharacterLine(dData, 9, "Ovo", "Where did I put that orb?"); + bb_setCharacterLine(dData, 10, "Ovo", "hmmm..."); + bb_setCharacterLine(dData, 11, "Ovo", "What about in the freezer?"); + bb_setCharacterLine(dData, 12, "Ovo", "I've checked every inch of the death dumpster."); + bb_setCharacterLine(dData, 13, "Ovo", "Glitch my circuits!"); + bb_setCharacterLine(dData, 14, "Ovo", "It must have gone out with the trash last Wednesday."); + bb_setCharacterLine(dData, 15, "Ovo", "Can I get an F in the chat?"); + bb_setCharacterLine(dData, 16, "Ovo", "..."); + bb_setCharacterLine(dData, 17, "Ovo", "The chaos orb is three times denser than a black hole."); + bb_setCharacterLine(dData, 18, "Ovo", + "Well if Garbotnik Sanitation Industries took it to the landfill, then it is definitely at the " + "VERY BOTTOM of the dump."); + bb_setCharacterLine(dData, 19, "Ovo", "Not a problem."); + bb_setCharacterLine(dData, 20, "Ovo", "We have the technology to retrieve it."); + bb_setCharacterLine( + dData, 21, "Ovo", + "Safety first. Activate the cloning machine in case I should perish on that nuclear wasteland."); + bb_setCharacterLine(dData, 22, "Ovo", "fine."); + bb_setCharacterLine(dData, 23, "Ovo", "Stupid safety rules. YOLO!"); + + dData->curString = -1; + + dData->endDialogueCB = &bb_afterGarbotnikIntro; + + bb_setData(ovo, dData, DIALOGUE_DATA); + } } void bb_startGarbotnikLandingTalk(bb_entity_t* self) @@ -4917,217 +4982,221 @@ void bb_startGarbotnikLandingTalk(bb_entity_t* self) bb_entity_t* ovo = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, OVO_TALK, 1, self->gameData->camera.camera.pos.x, self->gameData->camera.camera.pos.y, false, true); - - bb_dialogueData_t* dData = bb_createDialogueData(1, "Ovo"); - - int16_t phraseIdx = 0; - int16_t arraySize = sizeof(gData->landingPhrases) / sizeof(gData->landingPhrases[0]); - while (gData->landingPhrases[phraseIdx] == -1) + if(ovo != NULL) { - if (phraseIdx == arraySize - 1) + bb_dialogueData_t* dData = bb_createDialogueData(1, "Ovo"); + + int16_t phraseIdx = 0; + int16_t arraySize = sizeof(gData->landingPhrases) / sizeof(gData->landingPhrases[0]); + while (gData->landingPhrases[phraseIdx] == -1) { - // We've reached the end of saying everything. - // create sequential numbers of all phrase indices - for (int16_t i = 0; i < arraySize; i++) - gData->landingPhrases[i] = i; - // shuffle the array - for (int16_t i = arraySize - 1; i > 0; i--) + if (phraseIdx == arraySize - 1) { - int16_t j = (int16_t)(bb_randomInt(0, INT_MAX) % (i + 1)); - int16_t temp = gData->landingPhrases[i]; - gData->landingPhrases[i] = gData->landingPhrases[j]; - gData->landingPhrases[j] = temp; + // We've reached the end of saying everything. + // create sequential numbers of all phrase indices + for (int16_t i = 0; i < arraySize; i++) + gData->landingPhrases[i] = i; + // shuffle the array + for (int16_t i = arraySize - 1; i > 0; i--) + { + int16_t j = (int16_t)(bb_randomInt(0, INT_MAX) % (i + 1)); + int16_t temp = gData->landingPhrases[i]; + gData->landingPhrases[i] = gData->landingPhrases[j]; + gData->landingPhrases[j] = temp; + } + phraseIdx = 0; + } + else + { + phraseIdx++; } - phraseIdx = 0; - } - else - { - phraseIdx++; - } - } - switch (gData->landingPhrases[phraseIdx]) - { - case 0: - { - // Max dialogue string roughly: here----V - bb_setCharacterLine(dData, 0, "Ovo", "Ah, sweet stench! How I've longed for your olfactory embrace."); - break; - } - case 1: - { - bb_setCharacterLine(dData, 0, "Ovo", - "Tonight's special: Beetle Bruschetta with a side of centipede salad!"); - break; - } - case 2: - { - bb_setCharacterLine(dData, 0, "Ovo", "Another day, another dump full of delectable delights!"); - break; - } - case 3: - { - bb_setCharacterLine(dData, 0, "Ovo", "Step aside, garbage! The doctor is in!"); - break; - } - case 4: - { - bb_setCharacterLine(dData, 0, "Ovo", "I must find that chaos orb at all costs!"); - break; - } - case 5: - { - bb_setCharacterLine(dData, 0, "Ovo", "The Pango pest must be stopped!"); - break; - } - case 6: - { - bb_setCharacterLine(dData, 0, "Ovo", "I'll get you next time Pango! NEXT TIME!"); - break; - } - case 7: - { - bb_setCharacterLine(dData, 0, "Ovo", "Would you look at the time... It's GARBAGE TIME!"); - break; - } - case 8: - { - bb_setCharacterLine(dData, 0, "Ovo", "Did I remember to wax my moustache today?"); - break; - } - case 9: - { - bb_setCharacterLine(dData, 0, "Ovo", "Is it spelled \"moustache\" or \"mustache?\" Better look it up..."); - break; - } - case 10: - { - bb_setCharacterLine(dData, 0, "Ovo", "I wonder how my buddy Hank Waddle is holding up..."); - break; - } - case 11: - { - bb_setCharacterLine(dData, 0, "Ovo", "Man, I sure hope I see some cool bugs today!"); - break; - } - case 12: - { - bb_setCharacterLine(dData, 0, "Ovo", "I'm here to take in the trash."); - break; - } - case 13: - { - bb_setCharacterLine(dData, 0, "Ovo", - "I must remember to research what dastardly technology this dump used to ensure new " - "arrivals wind up at the bottom..."); - break; - } - case 14: - { - bb_setCharacterLine(dData, 0, "Ovo", - "I promise, this is perfectly sanitary, the Chaos Orb has antimicrobial properties."); - break; - } - case 15: - { - bb_setCharacterLine(dData, 0, "Ovo", "Of course you can trust me; I'm a doctor."); - break; - } - case 16: - { - bb_setCharacterLine(dData, 0, "Ovo", "Come on and jump and welcome to the dump!"); - break; - } - case 17: - { - bb_setCharacterLine(dData, 0, "Ovo", "Oh, I'd been looking for that garbage ray!"); - break; - } - case 18: - { - bb_setCharacterLine( - dData, 0, "Ovo", - "If you ask me what I have a degree in one more time, I'm asking the Internet to draw fanart of you."); - break; - } - case 19: - { - bb_setCharacterLine(dData, 0, "Ovo", "Oh, I'd been looking for that garbage ray!"); - break; - } - case 20: - { - bb_setCharacterLine(dData, 0, "Ovo", "Did I remember to turn off the disintegrator before I left the lab?"); - break; - } - case 21: - { - bb_setCharacterLine(dData, 0, "Ovo", "Oooh piece of candy!"); - break; - } - case 22: - { - bb_setCharacterLine(dData, 0, "Ovo", - "If they beat this game fast enough, does that mean I too have to strip?"); - break; - } - case 23: - { - bb_setCharacterLine(dData, 0, "Ovo", "Sluuurrrrrrrrrrp"); - break; - } - case 24: - { - bb_setCharacterLine(dData, 0, "Ovo", "A chili dog? No no, I've already had my... FILL."); - break; - } - case 25: - { - bb_setCharacterLine(dData, 0, "Ovo", "CHAOS... CONT.... oh, I appear to have been sent a C&D."); - break; - } - case 26: - { - bb_setCharacterLine(dData, 0, "Ovo", "I didn't like Clone #61 much anyway."); - break; - } - case 27: - { - bb_setCharacterLine(dData, 0, "Ovo", "Remember, Ovo is pronounced like 'oooh-voo' with a wink at the end."); - break; - } - case 28: - { - bb_setCharacterLine(dData, 0, "Ovo", "You don't have enough badge ribbons to train me."); - break; } - default: + switch (gData->landingPhrases[phraseIdx]) { - break; + case 0: + { + // Max dialogue string roughly: here----V + bb_setCharacterLine(dData, 0, "Ovo", "Ah, sweet stench! How I've longed for your olfactory embrace."); + break; + } + case 1: + { + bb_setCharacterLine(dData, 0, "Ovo", + "Tonight's special: Beetle Bruschetta with a side of centipede salad!"); + break; + } + case 2: + { + bb_setCharacterLine(dData, 0, "Ovo", "Another day, another dump full of delectable delights!"); + break; + } + case 3: + { + bb_setCharacterLine(dData, 0, "Ovo", "Step aside, garbage! The doctor is in!"); + break; + } + case 4: + { + bb_setCharacterLine(dData, 0, "Ovo", "I must find that chaos orb at all costs!"); + break; + } + case 5: + { + bb_setCharacterLine(dData, 0, "Ovo", "The Pango pest must be stopped!"); + break; + } + case 6: + { + bb_setCharacterLine(dData, 0, "Ovo", "I'll get you next time Pango! NEXT TIME!"); + break; + } + case 7: + { + bb_setCharacterLine(dData, 0, "Ovo", "Would you look at the time... It's GARBAGE TIME!"); + break; + } + case 8: + { + bb_setCharacterLine(dData, 0, "Ovo", "Did I remember to wax my moustache today?"); + break; + } + case 9: + { + bb_setCharacterLine(dData, 0, "Ovo", "Is it spelled \"moustache\" or \"mustache?\" Better look it up..."); + break; + } + case 10: + { + bb_setCharacterLine(dData, 0, "Ovo", "I wonder how my buddy Hank Waddle is holding up..."); + break; + } + case 11: + { + bb_setCharacterLine(dData, 0, "Ovo", "Man, I sure hope I see some cool bugs today!"); + break; + } + case 12: + { + bb_setCharacterLine(dData, 0, "Ovo", "I'm here to take in the trash."); + break; + } + case 13: + { + bb_setCharacterLine(dData, 0, "Ovo", + "I must remember to research what dastardly technology this dump used to ensure new " + "arrivals wind up at the bottom..."); + break; + } + case 14: + { + bb_setCharacterLine(dData, 0, "Ovo", + "I promise, this is perfectly sanitary, the Chaos Orb has antimicrobial properties."); + break; + } + case 15: + { + bb_setCharacterLine(dData, 0, "Ovo", "Of course you can trust me; I'm a doctor."); + break; + } + case 16: + { + bb_setCharacterLine(dData, 0, "Ovo", "Come on and jump and welcome to the dump!"); + break; + } + case 17: + { + bb_setCharacterLine(dData, 0, "Ovo", "Oh, I'd been looking for that garbage ray!"); + break; + } + case 18: + { + bb_setCharacterLine( + dData, 0, "Ovo", + "If you ask me what I have a degree in one more time, I'm asking the Internet to draw fanart of you."); + break; + } + case 19: + { + bb_setCharacterLine(dData, 0, "Ovo", "Oh, I'd been looking for that garbage ray!"); + break; + } + case 20: + { + bb_setCharacterLine(dData, 0, "Ovo", "Did I remember to turn off the disintegrator before I left the lab?"); + break; + } + case 21: + { + bb_setCharacterLine(dData, 0, "Ovo", "Oooh piece of candy!"); + break; + } + case 22: + { + bb_setCharacterLine(dData, 0, "Ovo", + "If they beat this game fast enough, does that mean I too have to strip?"); + break; + } + case 23: + { + bb_setCharacterLine(dData, 0, "Ovo", "Sluuurrrrrrrrrrp"); + break; + } + case 24: + { + bb_setCharacterLine(dData, 0, "Ovo", "A chili dog? No no, I've already had my... FILL."); + break; + } + case 25: + { + bb_setCharacterLine(dData, 0, "Ovo", "CHAOS... CONT.... oh, I appear to have been sent a C&D."); + break; + } + case 26: + { + bb_setCharacterLine(dData, 0, "Ovo", "I didn't like Clone #61 much anyway."); + break; + } + case 27: + { + bb_setCharacterLine(dData, 0, "Ovo", "Remember, Ovo is pronounced like 'oooh-voo' with a wink at the end."); + break; + } + case 28: + { + bb_setCharacterLine(dData, 0, "Ovo", "You don't have enough badge ribbons to train me."); + break; + } + default: + { + break; + } } - } - dData->curString = -1; - dData->endDialogueCB = &bb_afterGarbotnikLandingTalk; + dData->curString = -1; + dData->endDialogueCB = &bb_afterGarbotnikLandingTalk; - bb_setData(ovo, dData, DIALOGUE_DATA); + bb_setData(ovo, dData, DIALOGUE_DATA); + } } void bb_startGarbotnikCloningTalk(bb_entity_t* self) { bb_entity_t* ovo = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, OVO_TALK, 1, 0, 0, false, true); + if(ovo != NULL) + { + bb_dialogueData_t* dData = bb_createDialogueData(2, "Ovo"); // 29 - bb_dialogueData_t* dData = bb_createDialogueData(2, "Ovo"); // 29 - - bb_setCharacterLine(dData, 0, "Ovo", "I'm feeling fresh, baby!"); // V longest possible string here - bb_setCharacterLine(dData, 1, "Ovo", - "It was a good move taking those omega3 fish oils before backing up my brain."); + bb_setCharacterLine(dData, 0, "Ovo", "I'm feeling fresh, baby!"); // V longest possible string here + bb_setCharacterLine(dData, 1, "Ovo", + "It was a good move taking those omega3 fish oils before backing up my brain."); - dData->curString = -1; + dData->curString = -1; - dData->endDialogueCB = &bb_afterGarbotnikIntro; + dData->endDialogueCB = &bb_afterGarbotnikIntro; - bb_setData(ovo, dData, DIALOGUE_DATA); + bb_setData(ovo, dData, DIALOGUE_DATA); + } } void bb_startGarbotnikEggTutorialTalk(bb_entity_t* self) @@ -5135,18 +5204,20 @@ void bb_startGarbotnikEggTutorialTalk(bb_entity_t* self) bb_entity_t* ovo = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, OVO_TALK, 1, self->gameData->camera.camera.pos.x, self->gameData->camera.camera.pos.y, false, true); + if(ovo != NULL) + { + bb_dialogueData_t* dData = bb_createDialogueData(2, "Ovo"); - bb_dialogueData_t* dData = bb_createDialogueData(2, "Ovo"); - - // Max dialogue string roughly: here----V - bb_setCharacterLine(dData, 0, "Ovo", "Oooey Gooey! Look at that egg sack!"); - bb_setCharacterLine(dData, 1, "Ovo", - "I can use the directional buttons on my swadge to fly over there and check it out."); + // Max dialogue string roughly: here----V + bb_setCharacterLine(dData, 0, "Ovo", "Oooey Gooey! Look at that egg sack!"); + bb_setCharacterLine(dData, 1, "Ovo", + "I can use the directional buttons on my swadge to fly over there and check it out."); - dData->curString = -1; - dData->endDialogueCB = &bb_afterGarbotnikEggTutorialTalk; + dData->curString = -1; + dData->endDialogueCB = &bb_afterGarbotnikEggTutorialTalk; - bb_setData(ovo, dData, DIALOGUE_DATA); + bb_setData(ovo, dData, DIALOGUE_DATA); + } } void bb_startGarbotnikFuelTutorialTalk(bb_entity_t* self) @@ -5157,26 +5228,28 @@ void bb_startGarbotnikFuelTutorialTalk(bb_entity_t* self) bb_entity_t* ovo = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, OVO_TALK, 1, self->gameData->camera.camera.pos.x, self->gameData->camera.camera.pos.y, false, true); + if(ovo != NULL) + { + bb_dialogueData_t* dData = bb_createDialogueData(5, "Ovo"); + + // longest possible string " " + // bb_setCharacterLine(dData, 0, "A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A + // A A A A A A A A A A A A A A A A A A A"); + bb_setCharacterLine(dData, 0, "Ovo", + "When I travel away from the booster, I've got to keep an eye on my fuel level at all times."); + bb_setCharacterLine(dData, 1, "Ovo", + "Sit back atop the booster before all the lights around the outside of the swadge turn off."); + bb_setCharacterLine(dData, 2, "Ovo", + "safety first. Once back on the rocket, it takes a stupid long time for the " + "launch sequence to initiate."); + bb_setCharacterLine(dData, 3, "Ovo", "Too many regulations on equipment these days."); + bb_setCharacterLine(dData, 4, "Ovo", "Let a trashman go to space in peace."); - bb_dialogueData_t* dData = bb_createDialogueData(5, "Ovo"); - - // longest possible string " " - // bb_setCharacterLine(dData, 0, "A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A - // A A A A A A A A A A A A A A A A A A A"); - bb_setCharacterLine(dData, 0, "Ovo", - "When I travel away from the booster, I've got to keep an eye on my fuel level at all times."); - bb_setCharacterLine(dData, 1, "Ovo", - "Sit back atop the booster before all the lights around the outside of the swadge turn off."); - bb_setCharacterLine(dData, 2, "Ovo", - "safety first. Once back on the rocket, it takes a stupid long time for the " - "launch sequence to initiate."); - bb_setCharacterLine(dData, 3, "Ovo", "Too many regulations on equipment these days."); - bb_setCharacterLine(dData, 4, "Ovo", "Let a trashman go to space in peace."); - - dData->curString = -1; - dData->endDialogueCB = &bb_afterGarbotnikTutorialTalk; + dData->curString = -1; + dData->endDialogueCB = &bb_afterGarbotnikTutorialTalk; - bb_setData(ovo, dData, DIALOGUE_DATA); + bb_setData(ovo, dData, DIALOGUE_DATA); + } } void bb_afterGarbotnikTutorialTalk(bb_entity_t* self) @@ -5240,16 +5313,18 @@ void bb_afterGarbotnikLandingTalk(bb_entity_t* self) bb_entity_t* ovo = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, OVO_TALK, 1, self->gameData->camera.camera.pos.x, self->gameData->camera.camera.pos.y, false, true); + if(ovo != NULL) + { + bb_dialogueData_t* dData = bb_createDialogueData(2, "Ovo"); - bb_dialogueData_t* dData = bb_createDialogueData(2, "Ovo"); - - bb_setCharacterLine(dData, 0, "Ovo", - "If I find my old booster, I can recover any unprocessed bugs and donuts."); - bb_setCharacterLine(dData, 1, "Ovo", "Just push steadily into the derelict booster to coax them out."); + bb_setCharacterLine(dData, 0, "Ovo", + "If I find my old booster, I can recover any unprocessed bugs and donuts."); + bb_setCharacterLine(dData, 1, "Ovo", "Just push steadily into the derelict booster to coax them out."); - dData->curString = -1; - dData->endDialogueCB = &bb_afterGarbotnikTutorialTalk; - bb_setData(ovo, dData, DIALOGUE_DATA); + dData->curString = -1; + dData->endDialogueCB = &bb_afterGarbotnikTutorialTalk; + bb_setData(ovo, dData, DIALOGUE_DATA); + } } } @@ -5273,21 +5348,25 @@ void bb_afterGarbotnikLandingTalk(bb_entity_t* self) bb_entity_t* curEntity = &self->gameData->entityManager.entities[i]; if (curEntity->spriteIndex == EGG_LEAVES) { + bb_ensureEntitySpace(&self->gameData->entityManager, 1); self->gameData->entityManager.viewEntity = bb_createEntity(&(self->gameData->entityManager), NO_ANIMATION, true, NO_SPRITE_POI, 1, (self->gameData->entityManager.playerEntity->pos.x >> DECIMAL_BITS), (self->gameData->entityManager.playerEntity->pos.y >> DECIMAL_BITS), false, false); - bb_goToData* tData = (bb_goToData*)self->gameData->entityManager.viewEntity->data; - tData->tracking = curEntity; - - tData->midPointSqDist - = sqMagVec2d(divVec2d((vec_t){(tData->tracking->pos.x >> DECIMAL_BITS) - - (self->gameData->entityManager.viewEntity->pos.x >> DECIMAL_BITS), - (tData->tracking->pos.y >> DECIMAL_BITS) - - (self->gameData->entityManager.viewEntity->pos.y >> DECIMAL_BITS)}, - 2)); - tData->executeOnArrival = &bb_startGarbotnikEggTutorialTalk; - self->gameData->entityManager.viewEntity->updateFunction = &bb_updatePOI; + if(self->gameData->entityManager.viewEntity != NULL) + { + bb_goToData* tData = (bb_goToData*)self->gameData->entityManager.viewEntity->data; + tData->tracking = curEntity; + + tData->midPointSqDist + = sqMagVec2d(divVec2d((vec_t){(tData->tracking->pos.x >> DECIMAL_BITS) + - (self->gameData->entityManager.viewEntity->pos.x >> DECIMAL_BITS), + (tData->tracking->pos.y >> DECIMAL_BITS) + - (self->gameData->entityManager.viewEntity->pos.y >> DECIMAL_BITS)}, + 2)); + tData->executeOnArrival = &bb_startGarbotnikEggTutorialTalk; + self->gameData->entityManager.viewEntity->updateFunction = &bb_updatePOI; + } break; } } @@ -5411,6 +5490,7 @@ void bb_triggerGameOver(bb_entity_t* self) self->gameData->camera.camera.pos = (vec_t){(self->gameData->entityManager.deathDumpster->pos.x >> DECIMAL_BITS), (self->gameData->entityManager.deathDumpster->pos.y >> DECIMAL_BITS)}; + bb_ensureEntitySpace(&self->gameData->entityManager, 1); self->gameData->entityManager.viewEntity = bb_createEntity(&(self->gameData->entityManager), NO_ANIMATION, true, NO_SPRITE_POI, 1, self->gameData->camera.camera.pos.x, self->gameData->camera.camera.pos.y, false, false); @@ -5508,7 +5588,10 @@ void bb_bugDeath(bb_entity_t* self, bb_hitInfo_t* hitInfo) bb_entity_t* hitEffect = bb_createEntity(&(self->gameData->entityManager), ONESHOT_ANIMATION, false, BUMP_ANIM, 6, hitInfo->pos.x >> DECIMAL_BITS, hitInfo->pos.y >> DECIMAL_BITS, true, false); - hitEffect->drawFunction = &bb_drawHitEffect; + if(hitEffect != NULL) + { + hitEffect->drawFunction = &bb_drawHitEffect; + } } if (self->dataType == WALKING_BUG_DATA) @@ -5556,6 +5639,7 @@ void bb_cartDeath(bb_entity_t* self, bb_hitInfo_t* hitInfo) case BB_DONUT: { // spawn a donut as a reward for completing the fight + bb_ensureEntitySpace(&self->gameData->entityManager, 1); bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, BB_DONUT, 1, (self->pos.x >> DECIMAL_BITS), (self->pos.y >> DECIMAL_BITS), true, false); break; @@ -5563,6 +5647,7 @@ void bb_cartDeath(bb_entity_t* self, bb_hitInfo_t* hitInfo) default: // BB_SWADGE { // spawn a swadge as a reward for completing the fight + bb_ensureEntitySpace(&self->gameData->entityManager, 1); bb_createEntity(&self->gameData->entityManager, LOOPING_ANIMATION, false, BB_SWADGE, 9, (self->pos.x >> DECIMAL_BITS), (self->pos.y >> DECIMAL_BITS), true, false); break; @@ -5573,7 +5658,10 @@ void bb_cartDeath(bb_entity_t* self, bb_hitInfo_t* hitInfo) bb_entity_t* hitEffect = bb_createEntity(&(self->gameData->entityManager), ONESHOT_ANIMATION, false, BUMP_ANIM, 6, hitInfo->pos.x >> DECIMAL_BITS, hitInfo->pos.y >> DECIMAL_BITS, true, false); - hitEffect->drawFunction = &bb_drawHitEffect; + if(hitEffect != NULL) + { + hitEffect->drawFunction = &bb_drawHitEffect; + } } void bb_spawnHorde(bb_entity_t* self, uint8_t numBugs) @@ -5643,6 +5731,7 @@ void bb_spawnHorde(bb_entity_t* self, uint8_t numBugs) } if (consecutiveGarbage == 2) { + bb_ensureEntitySpace(&self->gameData->entityManager, 1); bb_entity_t* jankyBugDig = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, BB_JANKY_BUG_DIG, 1, (spawnPos.x << 5) + 16, (spawnPos.y << 5) + 16, false, false); @@ -5652,6 +5741,7 @@ void bb_spawnHorde(bb_entity_t* self, uint8_t numBugs) return; } + bb_ensureEntitySpace(&self->gameData->entityManager, 1); bb_entity_t* bug = bb_createEntity(&self->gameData->entityManager, LOOPING_ANIMATION, false, bb_randomInt(8, 13), 1, (spawnPos.x << 5) + 16, (spawnPos.y << 5) + 16, false, false); @@ -5727,54 +5817,65 @@ void bb_spawnHorde(bb_entity_t* self, uint8_t numBugs) void bb_trigger501kg(bb_entity_t* self) { - bb_loadSprite("501kg", 1, 1, &self->gameData->entityManager.sprites[BB_501KG]); - + bb_ensureEntitySpace(&self->gameData->entityManager, 1); bb_entity_t* missile = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, BB_501KG, 1, self->pos.x >> DECIMAL_BITS, self->pos.y >> DECIMAL_BITS, true, false); - bb_501kgData_t* kgData = ((bb_501kgData_t*)missile->data); + if(missile != NULL) + { + bb_501kgData_t* kgData = ((bb_501kgData_t*)missile->data); - kgData->vel = (vec_t){bb_randomInt(-60, 60), 60}; + kgData->vel = (vec_t){bb_randomInt(-60, 60), 60}; - while (missile->pos.y > -2173) - { - missile->pos.x -= (self->gameData->elapsedUs >> 12) * kgData->vel.x; - missile->pos.y -= (self->gameData->elapsedUs >> 12) * kgData->vel.y; - } + while (missile->pos.y > -2173) + { + missile->pos.x -= (self->gameData->elapsedUs >> 12) * kgData->vel.x; + missile->pos.y -= (self->gameData->elapsedUs >> 12) * kgData->vel.y; + } - vecFl_t floatVel = {(float)kgData->vel.x, (float)kgData->vel.y}; + vecFl_t floatVel = {(float)kgData->vel.x, (float)kgData->vel.y}; - kgData->angle = (int16_t)(atan2f(floatVel.y, floatVel.x) * (180.0 / M_PI)); - kgData->angle += 270; - while (kgData->angle < 0) - { - kgData->angle += 360; - } - while (kgData->angle > 359) - { - kgData->angle -= 360; - } + kgData->angle = (int16_t)(atan2f(floatVel.y, floatVel.x) * (180.0 / M_PI)); + kgData->angle += 270; + while (kgData->angle < 0) + { + kgData->angle += 360; + } + while (kgData->angle > 359) + { + kgData->angle -= 360; + } - kgData->targetY = self->pos.y; + kgData->targetY = self->pos.y; + } } void bb_triggerFaultyWile(bb_entity_t* self) { + bb_ensureEntitySpace(&self->gameData->entityManager, 1); bb_entity_t* explosion = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, BB_EXPLOSION, 1, self->pos.x >> DECIMAL_BITS, self->pos.y >> DECIMAL_BITS, false, false); - bb_explosionData_t* eData = (bb_explosionData_t*)explosion->data; - eData->radius = 60; + if(explosion != NULL) + { + bb_explosionData_t* eData = (bb_explosionData_t*)explosion->data; + eData->radius = 60; + } } void bb_triggerDrillBotWile(bb_entity_t* self) { + bb_ensureEntitySpace(&self->gameData->entityManager, 1); bb_entity_t* drillBot = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, BB_DRILL_BOT, 1, self->pos.x >> DECIMAL_BITS, -800, false, false); - bb_drillBotData_t* dbData = (bb_drillBotData_t*)drillBot->data; - dbData->targetY = self->pos.y - 256; + if(drillBot != NULL) + { + bb_drillBotData_t* dbData = (bb_drillBotData_t*)drillBot->data; + dbData->targetY = self->pos.y - 256; + } } void bb_triggerAtmosphericAtomizerWile(bb_entity_t* self) { + bb_ensureEntitySpace(&self->gameData->entityManager, 1); bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, BB_ATMOSPHERIC_ATOMIZER, 1, self->pos.x >> DECIMAL_BITS, self->pos.y >> DECIMAL_BITS, false, false); if (self->gameData->entityManager.playerEntity != NULL @@ -5787,18 +5888,21 @@ void bb_triggerAtmosphericAtomizerWile(bb_entity_t* self) void bb_triggerAmmoSupplyWile(bb_entity_t* self) { + bb_ensureEntitySpace(&self->gameData->entityManager, 1); bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, BB_AMMO_SUPPLY, 1, self->pos.x >> DECIMAL_BITS, -800, false, false); } void bb_triggerPacifierWile(bb_entity_t* self) { + bb_ensureEntitySpace(&self->gameData->entityManager, 1); bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, BB_PACIFIER, 1, self->pos.x >> DECIMAL_BITS, -800, false, false); } void bb_triggerSpaceLaserWile(bb_entity_t* self) { + bb_ensureEntitySpace(&self->gameData->entityManager, 1); bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, BB_SPACE_LASER, 1, self->pos.x >> DECIMAL_BITS, self->pos.y >> DECIMAL_BITS, false, false); } @@ -5838,6 +5942,7 @@ void bb_crumbleDirt(bb_gameData_t* gameData, uint8_t gameFramesPerAnimationFrame { vec_t tilePos = {.x = tile_i * TILE_SIZE + HALF_TILE, .y = tile_j * TILE_SIZE + HALF_TILE}; // create a bug + bb_ensureEntitySpace(&gameData->entityManager, 1); bb_entity_t* bug = bb_createEntity(&gameData->entityManager, LOOPING_ANIMATION, false, bb_randomInt(8, 13), 1, tilePos.x, tilePos.y, false, false); @@ -5869,6 +5974,7 @@ void bb_crumbleDirt(bb_gameData_t* gameData, uint8_t gameFramesPerAnimationFrame gameData->tilemap.fgTiles[tile_i][tile_j].embed = NOTHING_EMBED; // create fuel + bb_ensureEntitySpace(&gameData->entityManager, 1); bb_createEntity(&gameData->entityManager, LOOPING_ANIMATION, false, BB_FUEL, 10, tilePos.x, tilePos.y, true, false); break; diff --git a/main/modes/games/bigbug/mode_bigbug.c b/main/modes/games/bigbug/mode_bigbug.c index c1d3c6e16..3b938886c 100644 --- a/main/modes/games/bigbug/mode_bigbug.c +++ b/main/modes/games/bigbug/mode_bigbug.c @@ -326,120 +326,6 @@ static void bb_EnterModeSkipIntro(void) #endif -void bb_quickPlay(void) -{ - setFrameRateUs(16667); // 60 FPS - - // Force draw a loading screen - fillDisplayArea(0, 0, TFT_WIDTH, TFT_HEIGHT, c123); - - // Allocate memory for the game state - bigbug = heap_caps_calloc(1, sizeof(bb_t), MALLOC_CAP_SPIRAM); - - // calloc the columns in layers separately to avoid a big alloc - for (int32_t w = 0; w < TILE_FIELD_WIDTH; w++) - { - bigbug->gameData.tilemap.fgTiles[w] - = heap_caps_calloc(TILE_FIELD_HEIGHT, sizeof(bb_foregroundTileInfo_t), MALLOC_CAP_SPIRAM); - bigbug->gameData.tilemap.mgTiles[w] - = heap_caps_calloc(TILE_FIELD_HEIGHT, sizeof(bb_midgroundTileInfo_t), MALLOC_CAP_SPIRAM); - } - - // Allocate WSG loading helpers - bb_hsd = heatshrink_decoder_alloc(256, 8, 4); - // The largest image is bb_menu2.png, decodes to 99124 bytes - // 99328 is 1024 * 97 - bb_decodeSpace = heap_caps_malloc(99328, MALLOC_CAP_SPIRAM); - - bb_SetLeds(); - - // Load font - loadFont("ibm_vga8.font", &bigbug->gameData.font, false); - - const char loadingStr[] = "Loading..."; - int32_t tWidth = textWidth(&bigbug->gameData.font, loadingStr); - drawText(&bigbug->gameData.font, c542, loadingStr, (TFT_WIDTH - tWidth) / 2, - (TFT_HEIGHT - bigbug->gameData.font.height) / 2); - drawDisplayTft(NULL); - - bb_initializeGameData(&bigbug->gameData); - bb_initializeEntityManager(&bigbug->gameData.entityManager, &bigbug->gameData); - // Shrink after loading initial sprites. The next largest image is ovo_talk0.png, decodes to 67200 bytes - // 67584 is 1024 * 66 - bb_decodeSpace = heap_caps_realloc(bb_decodeSpace, 67584, MALLOC_CAP_SPIRAM); - - uint32_t deathDumpsterX = (TILE_FIELD_WIDTH / 2) * TILE_SIZE + HALF_TILE - 1; - uint32_t deathDumpsterY = -2173; - - // create 3 rockets - for (int rocketIdx = 0; rocketIdx < 3; rocketIdx++) - { - bigbug->gameData.entityManager.boosterEntities[rocketIdx] - = bb_createEntity(&bigbug->gameData.entityManager, NO_ANIMATION, true, ROCKET_ANIM, 16, - deathDumpsterX - 96 + 96 * rocketIdx, deathDumpsterY, false, true); - - // bigbug->gameData.entityManager.boosterEntities[rocketIdx]->updateFunction = NULL; - - if (rocketIdx >= 1) - { - bb_rocketData_t* rData = (bb_rocketData_t*)bigbug->gameData.entityManager.boosterEntities[rocketIdx]->data; - - rData->flame = bb_createEntity( - &(bigbug->gameData.entityManager), LOOPING_ANIMATION, false, FLAME_ANIM, 6, - bigbug->gameData.entityManager.boosterEntities[rocketIdx]->pos.x >> DECIMAL_BITS, - bigbug->gameData.entityManager.boosterEntities[rocketIdx]->pos.y >> DECIMAL_BITS, true, false); - - rData->flame->updateFunction = &bb_updateFlame; - } - else // rocketIdx == 0 - { - bigbug->gameData.entityManager.activeBooster = bigbug->gameData.entityManager.boosterEntities[rocketIdx]; - bigbug->gameData.entityManager.activeBooster->currentAnimationFrame = 40; - bigbug->gameData.entityManager.activeBooster->pos.y = 50; - bigbug->gameData.entityManager.activeBooster->updateFunction = bb_updateHeavyFalling; - bb_entity_t* arm = bb_createEntity( - &bigbug->gameData.entityManager, NO_ANIMATION, true, ATTACHMENT_ARM, 1, - bigbug->gameData.entityManager.activeBooster->pos.x >> DECIMAL_BITS, - (bigbug->gameData.entityManager.activeBooster->pos.y >> DECIMAL_BITS) - 33, true, false); - ((bb_attachmentArmData_t*)arm->data)->rocket = bigbug->gameData.entityManager.activeBooster; - - bb_entity_t* grabbyHand = bb_createEntity( - &bigbug->gameData.entityManager, LOOPING_ANIMATION, true, BB_GRABBY_HAND, 6, - bigbug->gameData.entityManager.activeBooster->pos.x >> DECIMAL_BITS, - (bigbug->gameData.entityManager.activeBooster->pos.y >> DECIMAL_BITS) - 53, true, false); - ((bb_grabbyHandData_t*)grabbyHand->data)->rocket = bigbug->gameData.entityManager.activeBooster; - } - } - - // create the death dumpster - bigbug->gameData.entityManager.deathDumpster - = bb_createEntity(&bigbug->gameData.entityManager, NO_ANIMATION, true, BB_DEATH_DUMPSTER, 1, deathDumpsterX, - deathDumpsterY, false, true); - - // create garbotnik's UI - bb_createEntity(&bigbug->gameData.entityManager, NO_ANIMATION, true, BB_GARBOTNIK_UI, 1, 0, 0, false, true); - - bigbug->gameData.entityManager.viewEntity - = bb_createEntity(&(bigbug->gameData.entityManager), NO_ANIMATION, true, GARBOTNIK_FLYING, 1, - bigbug->gameData.entityManager.activeBooster->pos.x >> DECIMAL_BITS, - (bigbug->gameData.entityManager.activeBooster->pos.y >> DECIMAL_BITS) - 90, true, false); - - bb_loadWsgs(&bigbug->gameData.tilemap); - - bigbug->gameData.entityManager.playerEntity = bigbug->gameData.entityManager.viewEntity; - - int8_t oldBoosterYs[3] = {-1, -1, -1}; - bb_generateWorld(&(bigbug->gameData.tilemap), oldBoosterYs); - - // Set the mode to game mode - bigbug->gameData.screen = BIGBUG_GAME; - - bb_setupMidi(); - unloadMidiFile(&bigbug->gameData.bgm); - loadMidiFile("BigBugExploration.mid", &bigbug->gameData.bgm, true); - globalMidiPlayerPlaySong(&bigbug->gameData.bgm, MIDI_BGM); -} - void bb_setupMidi(void) { // Setup MIDI @@ -1754,10 +1640,13 @@ static void bb_GameLoop(int64_t elapsedUs) = bb_createEntity(&bigbug->gameData.entityManager, NO_ANIMATION, true, BB_RADAR_PING, 1, bigbug->gameData.entityManager.playerEntity->pos.x >> DECIMAL_BITS, bigbug->gameData.entityManager.playerEntity->pos.y >> DECIMAL_BITS, true, false); - bigbug->gameData.screen = BIGBUG_GAME_PINGING; - bb_radarPingData_t* rpData = (bb_radarPingData_t*)radarPing->data; - rpData->color = c415; - rpData->executeAfterPing = &bb_openMap; + if(radarPing != NULL) + { + bigbug->gameData.screen = BIGBUG_GAME_PINGING; + bb_radarPingData_t* rpData = (bb_radarPingData_t*)radarPing->data; + rpData->color = c415; + rpData->executeAfterPing = &bb_openMap; + } } } } diff --git a/main/modes/games/bigbug/mode_bigbug.h b/main/modes/games/bigbug/mode_bigbug.h index 0ab3ac9cb..b2d63e57c 100644 --- a/main/modes/games/bigbug/mode_bigbug.h +++ b/main/modes/games/bigbug/mode_bigbug.h @@ -14,7 +14,6 @@ #ifndef _MODE_BIGBUG_H_ #define _MODE_BIGBUG_H_ -void bb_quickPlay(void); void bb_setupMidi(void); void bb_FreeTilemapData(void); From 4c66cc85d9a158cbf8273fae81a07e513795dea6 Mon Sep 17 00:00:00 2001 From: DebrisHauler Date: Sun, 19 Jan 2025 01:16:19 -0500 Subject: [PATCH 12/33] more null checks --- main/modes/games/bigbug/entity_bigbug.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/main/modes/games/bigbug/entity_bigbug.c b/main/modes/games/bigbug/entity_bigbug.c index e367d7097..6c729915c 100644 --- a/main/modes/games/bigbug/entity_bigbug.c +++ b/main/modes/games/bigbug/entity_bigbug.c @@ -1513,7 +1513,7 @@ void bb_updateBugShooting(bb_entity_t* self) // call it paused and update frames in it's own update function because this one uses another spriteIdx. bb_entity_t* spit = bb_createEntity(&self->gameData->entityManager, LOOPING_ANIMATION, true, BB_SPIT, 10, self->pos.x >> DECIMAL_BITS, self->pos.y >> DECIMAL_BITS, true, false); - if (self->gameData->entityManager.playerEntity != NULL) + if (self->gameData->entityManager.playerEntity != NULL && spit != NULL) { bb_spitData_t* sData = (bb_spitData_t*)spit->data; sData->vel = subVec2d(self->gameData->entityManager.playerEntity->pos, spit->pos); @@ -4977,6 +4977,10 @@ void bb_startGarbotnikIntro(bb_entity_t* self) void bb_startGarbotnikLandingTalk(bb_entity_t* self) { + if(self == NULL) + { + return; + } bb_garbotnikData_t* gData = (bb_garbotnikData_t*)self->data; bb_entity_t* ovo From cf7ec2f202fd4be356e85f7e5e97581e4e3d686d Mon Sep 17 00:00:00 2001 From: DebrisHauler Date: Sun, 19 Jan 2025 18:46:52 -0500 Subject: [PATCH 13/33] Final boss done. --- .../modes/games/bigbug/entityManager_bigbug.c | 34 ++ .../modes/games/bigbug/entityManager_bigbug.h | 2 +- main/modes/games/bigbug/entity_bigbug.c | 333 +++++++++++++++++- main/modes/games/bigbug/entity_bigbug.h | 20 ++ main/modes/games/bigbug/tilemap_bigbug.c | 11 + main/modes/games/bigbug/typedef_bigbug.h | 2 + main/modes/games/bigbug/worldGen_bigbug.c | 8 +- 7 files changed, 397 insertions(+), 13 deletions(-) diff --git a/main/modes/games/bigbug/entityManager_bigbug.c b/main/modes/games/bigbug/entityManager_bigbug.c index 4bca64f56..b79dc3d25 100644 --- a/main/modes/games/bigbug/entityManager_bigbug.c +++ b/main/modes/games/bigbug/entityManager_bigbug.c @@ -236,6 +236,10 @@ void bb_loadSprites(bb_entityManager_t* entityManager) entityManager->sprites[BB_HOTDOG].allocated = true; entityManager->sprites[BB_HOTDOG].brightnessLevels = 1; } + + //final boss is unloaded until needed. + entityManager->sprites[BB_FINAL_BOSS].originX = 30; + entityManager->sprites[BB_FINAL_BOSS].originY = 40; } void bb_updateEntities(bb_entityManager_t* entityManager, bb_camera_t* camera) @@ -1676,6 +1680,36 @@ bb_entity_t* bb_createEntity(bb_entityManager_t* entityManager, bb_animationType entity->drawFunction = &bb_drawQuickplay; break; } + case BB_FINAL_BOSS: + { + bb_finalBossData_t* fbData = heap_caps_calloc(1, sizeof(bb_finalBossData_t), MALLOC_CAP_SPIRAM); + fbData->health = 5500; + bb_setData(entity, fbData, FINAL_BOSS_DATA); + + entity->halfWidth = 15 << DECIMAL_BITS; + entity->halfHeight = 15 << DECIMAL_BITS; + + entity->collisions = heap_caps_calloc_tag(1, sizeof(list_t), MALLOC_CAP_SPIRAM, "rCollisions"); + list_t* others = heap_caps_calloc_tag(1, sizeof(list_t), MALLOC_CAP_SPIRAM, "rOthers"); + push(others, (void*)GARBOTNIK_FLYING); + bb_collision_t* collision + = heap_caps_calloc_tag(1, sizeof(bb_collision_t), MALLOC_CAP_SPIRAM, "rCollision"); + *collision = (bb_collision_t){others, bb_onCollisionSpaceLaserGarbotnik}; + push(entity->collisions, (void*)collision); + + list_t* others2 = heap_caps_calloc_tag(1, sizeof(list_t), MALLOC_CAP_SPIRAM, "rOthers"); + push(others2, (void*)HARPOON); + bb_collision_t* collision2 + = heap_caps_calloc_tag(1, sizeof(bb_collision_t), MALLOC_CAP_SPIRAM, "rCollision"); + *collision2 = (bb_collision_t){others2, bb_onCollisionBoss}; + push(entity->collisions, (void*)collision2); + + //just in time loading + bb_loadSprite("ovovoBoss", 8, 1, &entityManager->sprites[BB_FINAL_BOSS]); + entity->updateFunction = &bb_updateFinalBoss; + entity->drawFunction = &bb_drawFinalBoss; + break; + } default: // FLAME_ANIM and others need nothing set { break; diff --git a/main/modes/games/bigbug/entityManager_bigbug.h b/main/modes/games/bigbug/entityManager_bigbug.h index 4af705a73..02215b2de 100644 --- a/main/modes/games/bigbug/entityManager_bigbug.h +++ b/main/modes/games/bigbug/entityManager_bigbug.h @@ -18,7 +18,7 @@ //============================================================================== #define MAX_ENTITIES 200 #define MAX_FRONT_ENTITIES 10 -#define NUM_SPRITES 34 // The number of bb_sprite_t last accounted for BB_HOTDOG +#define NUM_SPRITES 35 // The number of bb_sprite_t last accounted for BB_FINAL_BOSS //============================================================================== // Structs diff --git a/main/modes/games/bigbug/entity_bigbug.c b/main/modes/games/bigbug/entity_bigbug.c index 6c729915c..26a68834a 100644 --- a/main/modes/games/bigbug/entity_bigbug.c +++ b/main/modes/games/bigbug/entity_bigbug.c @@ -2044,7 +2044,10 @@ void bb_updateCharacterTalk(bb_entity_t* self) freeWsg(&dData->sprite); freeWsg(&dData->spriteNext); int8_t characterSprite = 0; - if (strcmp(dData->characters[dData->curString], "Ovo") == 0) + if (strcmp(dData->characters[dData->curString], "Ovo") == 0 || + strcmp(dData->characters[dData->curString], "???") == 0 || + strcmp(dData->characters[dData->curString], "Ovo???") == 0 || + strcmp(dData->characters[dData->curString], "DOCTOR OVO") == 0) { characterSprite = bb_randomInt(0, 6); loadWsgInplace("dialogue_next.wsg", &dData->spriteNext, true, bb_decodeSpace, bb_hsd); // TODO free @@ -2280,7 +2283,6 @@ void bb_updateGrabbyHand(bb_entity_t* self) if (!(self->gameData->tutorialFlags & 0b100)) { - self->gameData->isPaused = true; // set the tutorial flag self->gameData->tutorialFlags |= 0b100; bb_entity_t* ovo = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, OVO_TALK, 1, @@ -3190,11 +3192,194 @@ void bb_updateQuickplay(bb_entity_t* self) } self->gameData->btnDownState = 0; self->gameData->btnState = 0; + + self->gameData->entityManager.playerEntity->pos.y = 181 << 9; + ((bb_garbotnikData_t*)self->gameData->entityManager.playerEntity->data)->numHarpoons = 250; + self->gameData->GarbotnikStat_fireTime = 50; + + // destroy self bb_destroyEntity(self, false, false); } } +void bb_updateFinalBoss(bb_entity_t* self) +{ + if(self->gameData->entityManager.playerEntity == NULL || FINAL_BOSS_DATA != self->dataType) + { + return; + } + + bb_finalBossData_t* fbData = (bb_finalBossData_t*)self->data; + if(fbData == NULL) + { + return; + } + + if(fbData->health <= 0) + { + self->gameData->isPaused = true; + bb_entity_t* ovo = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, OVO_TALK, 1, + self->gameData->camera.camera.pos.x, + self->gameData->camera.camera.pos.y, false, true); + if(ovo != NULL) + { + bb_dialogueData_t* dData = bb_createDialogueData(12, "Ovo"); + + bb_setCharacterLine(dData, 0, "Ovo", "Wow, I didn't think you would get this far."); + bb_setCharacterLine(dData, 1, "Ovo", "I'm about to grab the chaos orb, but I can see the future and the past now."); + bb_setCharacterLine(dData, 2, "Ovo", "So here's how it goes down."); + bb_setCharacterLine(dData, 3, "Ovo", + "With the chaos orb, I boot up the time machine but it malfunctions!"); + bb_setCharacterLine( + dData, 4, "Ovo", + "Stuck in the carboniferous period, I have to fight off the the biggest bugs Earth has ever known."); + bb_setCharacterLine(dData, 5, "Ovo", + "I had to shatter the chaos orb to freeze time and save myself."); + bb_setCharacterLine(dData, 6, "Ovo", "And that's how all these big bugs ended up in the G.S.I. dump in the 21st century."); + bb_setCharacterLine(dData, 7, "Ovo", + "And the calendar doesn't work right past January 2025."); + bb_setCharacterLine(dData, 8, "Ovo", + "But that's a future problem."); + bb_setCharacterLine(dData, 9, "Ovo", "It's all very confusing, I know."); + bb_setCharacterLine(dData, 10, "Ovo", "Anyways, thanks for playing the game!"); + bb_setCharacterLine(dData, 11, "Ovo", "This uiverse's timeline cannot process your awesomeness!"); + + dData->curString = -1; + dData->endDialogueCB = &bb_afterEnding; + bb_setData(ovo, dData, DIALOGUE_DATA); + } + + //destroy self + bb_destroyEntity(self, false, true); + return; + } + + if(!fbData->firstDialogeDone) + { + fbData->firstDialogeDone = true; + self->gameData->isPaused = true; + bb_entity_t* ovo = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, OVO_TALK, 1, + self->gameData->camera.camera.pos.x, + self->gameData->camera.camera.pos.y, false, true); + if(ovo != NULL) + { + bb_dialogueData_t* dData = bb_createDialogueData(6, "???"); + + bb_setCharacterLine(dData, 0, "???", "muahahahahaha!"); + bb_setCharacterLine(dData, 1, "Ovo", "Who's there?"); + bb_setCharacterLine(dData, 2, "???", "..."); + bb_setCharacterLine(dData, 3, "Ovo", + "Who the heck else is down here?"); + bb_setCharacterLine(dData, 4, "???", "..."); + bb_setCharacterLine(dData, 5, "???", "Right. On. Time."); + + dData->endDialogueCB = &bb_afterGarbotnikTutorialTalk; + dData->curString = -1; + bb_setData(ovo, dData, DIALOGUE_DATA); + } + } + else if(!fbData->secondDialogeDone && fbData->health < 3500) + { + fbData->secondDialogeDone = true; + self->gameData->isPaused = true; + bb_entity_t* ovo = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, OVO_TALK, 1, + self->gameData->camera.camera.pos.x, + self->gameData->camera.camera.pos.y, false, true); + if(ovo != NULL) + { + bb_dialogueData_t* dData = bb_createDialogueData(16, "Ovo"); + + bb_setCharacterLine(dData, 0, "Ovo", "WHO ARE YOU?"); + bb_setCharacterLine(dData, 1, "Ovo???", "HA! I'm future you from the past!"); + bb_setCharacterLine(dData, 2, "Ovo", "How is this possible?"); + bb_setCharacterLine(dData, 3, "Ovo???", + "Do you think Garbotnik stays in heaven because he, too, lives in fear of what he's created here on earth?"); + bb_setCharacterLine(dData, 4, "Ovo", "That actually makes no sense. "); + bb_setCharacterLine(dData, 5, "Ovo???", "How could I expect YOU to understand?"); + bb_setCharacterLine(dData, 6, "DOCTOR OVO", "You're stuck living in the present, Ovo. And I have a PHD!"); + bb_setCharacterLine(dData, 7, "Ovo", "Oh nice! What will my degree be in?"); + bb_setCharacterLine(dData, 8, "DOCTOR OVO", "How naive!"); + bb_setCharacterLine(dData, 9, "DOCTOR OVO", "There are no fancy titles in death, fool."); + bb_setCharacterLine(dData, 10, "Ovo", "You watch your mouth! Because I'm the real O.G.!"); + bb_setCharacterLine(dData, 11, "Ovo", "WHERE'S MY CHAOS ORB?"); + bb_setCharacterLine(dData, 12, "DOCTOR OVO", "You're x days too late."); + bb_setCharacterLine(dData, 13, "DOCTOR OVO", "Now there's just one loose end I have to tie up."); + bb_setCharacterLine(dData, 14, "DOCTOR OVO", "AND IT'S YOU!"); + bb_setCharacterLine(dData, 15, "Ovo", "You think you can defeat me? I egg to differ."); + + dData->endDialogueCB = &bb_afterGarbotnikTutorialTalk; + dData->curString = -1; + bb_setData(ovo, dData, DIALOGUE_DATA); + } + } + + if (fbData->damageEffect > 0) + { + fbData->damageEffect -= self->gameData->elapsedUs >> 11; + } + + //get a normalized vector from self to the player + vec_t toPlayer = subVec2d(self->gameData->entityManager.playerEntity->pos, self->pos); + fastNormVec(&toPlayer.x, &toPlayer.y); + fbData->vel = addVec2d(fbData->vel, mulVec2d(toPlayer, self->gameData->elapsedUs >> 14)); + //apply drag to velocity + fbData->vel = divVec2d(fbData->vel, 2); + + //move the boss + self->pos = addVec2d(self->pos, divVec2d(mulVec2d(fbData->vel, self->gameData->elapsedUs >> 14),16)); + + for(int egg = 0; egg < 10; egg++) + { + if(fbData->bossEggs[egg] != NULL && fbData->bossEggs[egg]->active && EGG_LEAVES_DATA == fbData->bossEggs[egg]->dataType) + { + bb_eggLeavesData_t* elData = (bb_eggLeavesData_t*)fbData->bossEggs[egg]->data; + //calculate new egg pos relative to self. + + vec_t toEgg = subVec2d(fbData->bossEggs[egg]->pos, self->pos); + toEgg = rotateVec2d(toEgg, 1); + fastNormVec(&toEgg.x, &toEgg.y); + toEgg = mulVec2d(toEgg, 5); + fbData->bossEggs[egg]->pos = addVec2d(self->pos, toEgg); + elData->egg->pos = fbData->bossEggs[egg]->pos; + } + } + + //increment the stateTimer + fbData->stateTimer += self->gameData->elapsedUs >> 9; + if(fbData->stateTimer > 0) + { + uint8_t check = fbData->stateTimer / 3277; + if(fbData->bossEggs[check] == NULL || fbData->bossEggs[check]->active == false || EGG_LEAVES_DATA != fbData->bossEggs[check]->dataType) + { + bb_ensureEntitySpace(&self->gameData->entityManager, 2); + fbData->bossEggs[check] + = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, EGG_LEAVES, 1, + self->pos.x >> DECIMAL_BITS, self->pos.y >> DECIMAL_BITS, false, false); + if (fbData->bossEggs[check] != NULL) + { + ((bb_eggLeavesData_t*)fbData->bossEggs[check]->data)->egg = bb_createEntity( + &self->gameData->entityManager, NO_ANIMATION, true, EGG, 1, (fbData->bossEggs[check]->pos.x >> DECIMAL_BITS) - 200 + bb_randomInt(0, 400), + 195 << 5, false, false); + if (((bb_eggLeavesData_t*)fbData->bossEggs[check]->data)->egg == NULL) + { + bb_destroyEntity(fbData->bossEggs[check], false, true); + } + else + { + fbData->bossEggs[check] ->collisions = heap_caps_calloc_tag(1, sizeof(list_t), MALLOC_CAP_SPIRAM, "rCollisions"); + list_t* others = heap_caps_calloc_tag(1, sizeof(list_t), MALLOC_CAP_SPIRAM, "rOthers"); + push(others, (void*)GARBOTNIK_FLYING); + bb_collision_t* collision + = heap_caps_calloc_tag(1, sizeof(bb_collision_t), MALLOC_CAP_SPIRAM, "rCollision"); + *collision = (bb_collision_t){others, bb_onCollisionBossEgg}; + push(fbData->bossEggs[check]->collisions, (void*)collision); + } + } + } + } +} + void bb_drawGarbotnikFlying(bb_entityManager_t* entityManager, rectangle_t* camera, bb_entity_t* self) { if (GARBOTNIK_DATA != self->dataType) @@ -3604,6 +3789,12 @@ void bb_drawCharacterTalk(bb_entityManager_t* entityManager, rectangle_t* camera { textColor = c544; } + else if (strcmp(dData->characters[dData->curString], "???") == 0 || + strcmp(dData->characters[dData->curString], "Ovo???") == 0 || + strcmp(dData->characters[dData->curString], "DOCTOR OVO") == 0) + { + textColor = c550; + } if (dData->blinkTimer > 0) { // The sprites I took from UTT need to be drawn a little more to the left. @@ -4310,6 +4501,52 @@ void bb_drawQuickplay(bb_entityManager_t* entityManager, rectangle_t* camera, bb drawTextWordWrapCentered(&self->gameData->font, mData->selectionIdx == 3 ? c550 : c444, "YES", &xOff, &yOff, (TFT_WIDTH >> 1) + (TFT_WIDTH >> 2) + 40, (TFT_HEIGHT >> 1) + 60); } +void bb_drawFinalBoss(bb_entityManager_t* entityManager, rectangle_t* camera, bb_entity_t* self) +{ + if (FINAL_BOSS_DATA != self->dataType) + { + return; + } + + bb_finalBossData_t* fbData = (bb_finalBossData_t*)self->data; + + // draw the tow cables + for(int egg = 0; egg < 10; egg++) + { + if(fbData->bossEggs[egg] != NULL && fbData->bossEggs[egg]->active && EGG_LEAVES_DATA == fbData->bossEggs[egg]->dataType) + { + drawLineFast((self->pos.x >> DECIMAL_BITS) - camera->pos.x, (self->pos.y >> DECIMAL_BITS) - camera->pos.y, + (fbData->bossEggs[egg]->pos.x >> DECIMAL_BITS) - camera->pos.x, + (fbData->bossEggs[egg]->pos.y >> DECIMAL_BITS) - camera->pos.y, c425); + } + } + // node_t* current = gData->towedEntities.first; + // while (current != NULL) + // { + // bb_entity_t* curEntity = (bb_entity_t*)current->val; + // drawLineFast((self->pos.x >> DECIMAL_BITS) - camera->pos.x, (self->pos.y >> DECIMAL_BITS) - camera->pos.y, + // (curEntity->pos.x >> DECIMAL_BITS) - camera->pos.x, + // (curEntity->pos.y >> DECIMAL_BITS) - camera->pos.y, c425); + // current = current->next; + // } + + int16_t xOff = (self->pos.x >> DECIMAL_BITS) - entityManager->sprites[self->spriteIndex].originX - camera->pos.x; + int16_t yOff = (self->pos.y >> DECIMAL_BITS) - entityManager->sprites[self->spriteIndex].originY - camera->pos.y; + + // Draw the sprite + if (fbData->damageEffect > 70 || (fbData->damageEffect > 0 && bb_randomInt(0, 1))) + { + drawWsgPalette(&entityManager->sprites[self->spriteIndex].frames[self->currentAnimationFrame], xOff, yOff, + &self->gameData->damagePalette, fbData->vel.x > 0, false, 0); + } + else + { + drawWsg(&entityManager->sprites[self->spriteIndex].frames[self->currentAnimationFrame], xOff, yOff, fbData->vel.x > 0, false, 0); + } + + +} + // void bb_drawRect(bb_entityManager_t* entityManager, rectangle_t* camera, bb_entity_t* self) // { // drawRect (((self->pos.x - self->halfWidth) >>DECIMAL_BITS) - camera->pos.x, @@ -4326,8 +4563,40 @@ void bb_onCollisionHarpoon(bb_entity_t* self, bb_entity_t* other, bb_hitInfo_t* // pop that egg int32_t tile_i = other->pos.x >> 9; // 4 decimal bits and 5 bitshifts is divide by 32. int32_t tile_j = other->pos.y >> 9; // 4 decimal bits and 5 bitshifts is divide by 32. - self->gameData->tilemap.fgTiles[tile_i][tile_j].health = 0; - bb_crumbleDirt(other->gameData, 2, tile_i, tile_j, true, true); + if(self->gameData->tilemap.fgTiles[tile_i][tile_j].health == 0)//This case is for boss eggs that are in the air. + { + //spawn a bug + bb_ensureEntitySpace(&self->gameData->entityManager, 1); + bb_entity_t* bug = bb_createEntity(&self->gameData->entityManager, LOOPING_ANIMATION, false, + bb_randomInt(8, 13), 1, tile_i<<5, tile_j<<5, false, false); + + if (bug != NULL) + { + midiPlayer_t* sfx = soundGetPlayerSfx(); + midiPlayerReset(sfx); + soundPlaySfx(&self->gameData->sfxEgg, 0); + + if (self->gameData->tilemap.fgTiles[tile_i][tile_j].entity != NULL) + { + bb_entity_t* egg + = ((bb_eggLeavesData_t*)(self->gameData->tilemap.fgTiles[tile_i][tile_j].entity->data))->egg; + if (egg != NULL) + { + // destroy the egg + bb_destroyEntity(egg, false, true); + } + // destroy this (eggLeaves) + bb_destroyEntity(self->gameData->tilemap.fgTiles[tile_i][tile_j].entity, false, true); + } + self->gameData->tilemap.fgTiles[tile_i][tile_j].embed = NOTHING_EMBED; + } + bb_crumbleDirt(other->gameData, 2, tile_i, tile_j, true, true); + } + else + { + self->gameData->tilemap.fgTiles[tile_i][tile_j].health = 0; + bb_crumbleDirt(other->gameData, 2, tile_i, tile_j, true, true); + } // destroy this harpoon bb_destroyEntity(self, false, true); } @@ -4356,7 +4625,7 @@ void bb_onCollisionHarpoon(bb_entity_t* self, bb_entity_t* other, bb_hitInfo_t* { bb_dialogueData_t* dData = bb_createDialogueData(7, "Ovo"); - bb_setCharacterLine(dData, 0, "Ovo", "ARLIGHT! You got it!!!"); + bb_setCharacterLine(dData, 0, "Ovo", "ALRIGHT! You got it!!!"); bb_setCharacterLine(dData, 1, "Ovo", "Ahem... I mean, not bad."); bb_setCharacterLine(dData, 2, "Ovo", "In fact, I'm the one who got it."); bb_setCharacterLine(dData, 3, "Ovo", @@ -4724,11 +4993,6 @@ void bb_onCollisionSpit(bb_entity_t* self, bb_entity_t* other, bb_hitInfo_t* hit midiPlayer_t* sfx = soundGetPlayerSfx(); midiPlayerReset(sfx); soundPlaySfx(&self->gameData->sfxDamage, 0); - if (gData->fuel < 0) - { - gData->fuel - = 1; // It'll decrement soon anyways. Keeps more of the game over code on Garbotnik's side of the fence. - } } else // PHYSICS_DATA { @@ -4921,6 +5185,45 @@ void bb_onCollisionSpaceLaserBug(bb_entity_t* self, bb_entity_t* other, bb_hitIn } } +void bb_onCollisionBossEgg(bb_entity_t* self, bb_entity_t* other, bb_hitInfo_t* hitInfo) +{ + if (other->dataType != GARBOTNIK_DATA) + { + return; + } + bb_garbotnikData_t* gData = (bb_garbotnikData_t*)other->data; + gData->damageEffect = 100; + gData->fuel -= 10000; + midiPlayer_t* sfx = soundGetPlayerSfx(); + midiPlayerReset(sfx); + soundPlaySfx(&self->gameData->sfxDamage, 0); + // transform "hatch" into a bug... + bb_eggLeavesData_t* elData = (bb_eggLeavesData_t*)self->data; + // create a bug + bb_ensureEntitySpace(&self->gameData->entityManager, 1); + bb_entity_t* bug + = bb_createEntity(&self->gameData->entityManager, LOOPING_ANIMATION, false, bb_randomInt(8, 13), 1, + elData->egg->pos.x >> DECIMAL_BITS, elData->egg->pos.y >> DECIMAL_BITS, false, false); + if (bug != NULL) + { + // destroy the egg + bb_destroyEntity(elData->egg, false, true); + // destroy this + bb_destroyEntity(self, false, true); + } +} + +void bb_onCollisionBoss(bb_entity_t* self, bb_entity_t* other, bb_hitInfo_t* hitInfo) +{ + //damage self + bb_finalBossData_t* fbData = (bb_finalBossData_t*)self->data; + fbData->damageEffect = 100; + fbData->health -= 34; + + //destroy other + bb_destroyEntity(other, false, true); +} + void bb_startGarbotnikIntro(bb_entity_t* self) { bb_entity_t* ovo @@ -5819,6 +6122,11 @@ void bb_spawnHorde(bb_entity_t* self, uint8_t numBugs) } } +void bb_afterEnding(bb_entity_t* self) +{ + self->gameData->exit = true; +} + void bb_trigger501kg(bb_entity_t* self) { bb_ensureEntitySpace(&self->gameData->entityManager, 1); @@ -5997,7 +6305,10 @@ bb_dialogueData_t* bb_createDialogueData(uint8_t numStrings, const char* firstCh dData->numStrings = numStrings; dData->offsetY = -240; int8_t characterSprite = 0; - if (strcmp(firstCharacter, "Ovo") == 0) + if (strcmp(firstCharacter, "Ovo") == 0 || + strcmp(firstCharacter, "???") == 0 || + strcmp(firstCharacter, "Ovo???") == 0 || + strcmp(firstCharacter, "DOCTOR OVO") == 0) { characterSprite = bb_randomInt(0, 6); loadWsgInplace("dialogue_next.wsg", &dData->spriteNext, true, bb_decodeSpace, bb_hsd); diff --git a/main/modes/games/bigbug/entity_bigbug.h b/main/modes/games/bigbug/entity_bigbug.h index 1db55f171..e9c006ec5 100644 --- a/main/modes/games/bigbug/entity_bigbug.h +++ b/main/modes/games/bigbug/entity_bigbug.h @@ -52,6 +52,7 @@ typedef enum ATMOSPHERIC_ATOMIZER_DATA, DRILL_BOT_DATA, SPACE_LASER_DATA, + FINAL_BOSS_DATA, } bb_data_type_t; //============================================================================== @@ -226,11 +227,13 @@ typedef struct bb_entity_t* egg; // tracks the egg to stimulate it. } bb_eggLeavesData_t; + typedef struct { uint16_t stimulation; // once it reaches 600, it turns into a bug. } bb_eggData_t; + typedef struct { bb_entity_t* rocket; // a reference to the booster. To reposition self and Notify to take off at angle = 359. @@ -350,6 +353,18 @@ typedef struct uint8_t highestGarbage; } bb_spaceLaserData_t; +typedef struct +{ + vec_t vel; + int8_t damageEffect; // decrements over time. + int16_t health; + bb_entity_t* bossEggs[10]; + int16_t stateTimer; // increments over time. Doing some attacks when positive. Overflows intentionally. + bool firstDialogeDone; + bool secondDialogeDone; +} bb_finalBossData_t; + + typedef void (*bb_updateFunction_t)(bb_entity_t* self); typedef void (*bb_updateFarFunction_t)(bb_entity_t* self); typedef void (*bb_drawFunction_t)(bb_entityManager_t* entityManager, rectangle_t* camera, bb_entity_t* self); @@ -447,6 +462,7 @@ void bb_updateTimedPhysicsObject(bb_entity_t* self); void bb_updatePacifier(bb_entity_t* self); void bb_updateSpaceLaser(bb_entity_t* self); void bb_updateQuickplay(bb_entity_t* self); +void bb_updateFinalBoss(bb_entity_t* self); void bb_drawGarbotnikFlying(bb_entityManager_t* entityManager, rectangle_t* camera, bb_entity_t* self); void bb_drawHarpoon(bb_entityManager_t* entityManager, rectangle_t* camera, bb_entity_t* self); @@ -482,6 +498,7 @@ void bb_drawSpaceLaser(bb_entityManager_t* entityManager, rectangle_t* camera, b void bb_drawDeadBug(bb_entityManager_t* entityManager, rectangle_t* camera, bb_entity_t* self); void bb_drawGarbotnikUI(bb_entityManager_t* entityManager, rectangle_t* camera, bb_entity_t* self); void bb_drawQuickplay(bb_entityManager_t* entityManager, rectangle_t* camera, bb_entity_t* self); +void bb_drawFinalBoss(bb_entityManager_t* entityManager, rectangle_t* camera, bb_entity_t* self); // void bb_drawRect(bb_entityManager_t* entityManager, rectangle_t* camera, bb_entity_t* self); @@ -504,6 +521,8 @@ void bb_onCollisionAmmoSupply(bb_entity_t* self, bb_entity_t* other, bb_hitInfo_ void bb_onCollisionBrickTutorial(bb_entity_t* self, bb_entity_t* other, bb_hitInfo_t* hitInfo); void bb_onCollisionSpaceLaserGarbotnik(bb_entity_t* self, bb_entity_t* other, bb_hitInfo_t* hitInfo); void bb_onCollisionSpaceLaserBug(bb_entity_t* self, bb_entity_t* other, bb_hitInfo_t* hitInfo); +void bb_onCollisionBossEgg(bb_entity_t* self, bb_entity_t* other, bb_hitInfo_t* hitInfo); +void bb_onCollisionBoss(bb_entity_t* self, bb_entity_t* other, bb_hitInfo_t* hitInfo); // callbacks void bb_startGarbotnikIntro(bb_entity_t* self); @@ -525,6 +544,7 @@ void bb_playCarAlarm(bb_entity_t* self); void bb_bugDeath(bb_entity_t* self, bb_hitInfo_t* hitInfo); void bb_cartDeath(bb_entity_t* self, bb_hitInfo_t* hitInfo); void bb_spawnHorde(bb_entity_t* self, uint8_t numBugs); +void bb_afterEnding(bb_entity_t* self); void bb_crumbleDirt(bb_gameData_t* gameData, uint8_t gameFramesPerAnimationFrame, uint8_t tile_i, uint8_t tile_j, bool zeroHealth, bool flagNeighborsForPathfinding); diff --git a/main/modes/games/bigbug/tilemap_bigbug.c b/main/modes/games/bigbug/tilemap_bigbug.c index 39aca48eb..404b44018 100644 --- a/main/modes/games/bigbug/tilemap_bigbug.c +++ b/main/modes/games/bigbug/tilemap_bigbug.c @@ -388,6 +388,17 @@ void bb_drawTileMap(bb_tilemap_t* tilemap, rectangle_t* camera, vec_t* garbotnik } break; } + case FINAL_BOSS_EMBED: + { + bb_ensureEntitySpace(entityManager, 1); + if (bb_createEntity(entityManager, LOOPING_ANIMATION, false, BB_FINAL_BOSS, 4, + i * TILE_SIZE + HALF_TILE, j * TILE_SIZE + HALF_TILE, false, false) + != NULL) + { + tilemap->fgTiles[i][j].embed = NOTHING_EMBED; + } + break; + } default: { break; diff --git a/main/modes/games/bigbug/typedef_bigbug.h b/main/modes/games/bigbug/typedef_bigbug.h index 8d94a8042..fe2b5c78b 100644 --- a/main/modes/games/bigbug/typedef_bigbug.h +++ b/main/modes/games/bigbug/typedef_bigbug.h @@ -67,6 +67,7 @@ typedef enum BB_AMMO_SUPPLY, // A barrel of harpoons BB_PACIFIER, // Calms down the bugs in range. BB_HOTDOG, // icon for use in the radar screen. + BB_FINAL_BOSS, // These things do not have sprites in the sprites array. But we need the enum for the sake of the entity. // Some may have wsgs, but they cleverly load and unload their own WSGs. @@ -99,6 +100,7 @@ typedef enum BB_FOOD_CART_WITH_DONUT_EMBED, BB_FOOD_CART_WITH_SWADGE_EMBED, BRICK_TUTORIAL_EMBED, + FINAL_BOSS_EMBED, } bb_embeddable_t; typedef enum diff --git a/main/modes/games/bigbug/worldGen_bigbug.c b/main/modes/games/bigbug/worldGen_bigbug.c index 19e90bd8e..6aa01b2f5 100644 --- a/main/modes/games/bigbug/worldGen_bigbug.c +++ b/main/modes/games/bigbug/worldGen_bigbug.c @@ -116,7 +116,7 @@ void bb_generateWorld(bb_tilemap_t* tilemap, int8_t* oldBoosterYs) } } - // green value used for midground tiles and doors + // green value used for midground tiles and doors and final boss switch ((rgbCol >> 8) & 255) { case 51: @@ -132,6 +132,12 @@ void bb_generateWorld(bb_tilemap_t* tilemap, int8_t* oldBoosterYs) tilemap->fgTiles[i][j].embed = DOOR_EMBED; break; } + case 153: + { + tilemap->mgTiles[i][j].health = 0; + tilemap->fgTiles[i][j].embed = FINAL_BOSS_EMBED; + break; + } default: { tilemap->mgTiles[i][j].health = 0; From 7e72d1060eaf0df5613c7b94612b33ed339685ae Mon Sep 17 00:00:00 2001 From: DebrisHauler Date: Sun, 19 Jan 2025 18:56:36 -0500 Subject: [PATCH 14/33] Revert quick play to away from boss testing. --- main/modes/games/bigbug/entity_bigbug.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/main/modes/games/bigbug/entity_bigbug.c b/main/modes/games/bigbug/entity_bigbug.c index 26a68834a..e8600234d 100644 --- a/main/modes/games/bigbug/entity_bigbug.c +++ b/main/modes/games/bigbug/entity_bigbug.c @@ -3193,9 +3193,10 @@ void bb_updateQuickplay(bb_entity_t* self) self->gameData->btnDownState = 0; self->gameData->btnState = 0; - self->gameData->entityManager.playerEntity->pos.y = 181 << 9; - ((bb_garbotnikData_t*)self->gameData->entityManager.playerEntity->data)->numHarpoons = 250; - self->gameData->GarbotnikStat_fireTime = 50; + // for testing final boss + // self->gameData->entityManager.playerEntity->pos.y = 181 << 9; + // ((bb_garbotnikData_t*)self->gameData->entityManager.playerEntity->data)->numHarpoons = 250; + // self->gameData->GarbotnikStat_fireTime = 50; // destroy self @@ -3303,7 +3304,9 @@ void bb_updateFinalBoss(bb_entity_t* self) bb_setCharacterLine(dData, 9, "DOCTOR OVO", "There are no fancy titles in death, fool."); bb_setCharacterLine(dData, 10, "Ovo", "You watch your mouth! Because I'm the real O.G.!"); bb_setCharacterLine(dData, 11, "Ovo", "WHERE'S MY CHAOS ORB?"); - bb_setCharacterLine(dData, 12, "DOCTOR OVO", "You're x days too late."); + char voiceLine[30]; + snprintf(voiceLine, sizeof(voiceLine), "You're %d day%s too late.", self->gameData->day, self->gameData->day == 1 ? "" : "s"); + bb_setCharacterLine(dData, 12, "DOCTOR OVO", voiceLine); bb_setCharacterLine(dData, 13, "DOCTOR OVO", "Now there's just one loose end I have to tie up."); bb_setCharacterLine(dData, 14, "DOCTOR OVO", "AND IT'S YOU!"); bb_setCharacterLine(dData, 15, "Ovo", "You think you can defeat me? I egg to differ."); From 2cc70410eef72da8fb6daacad3cdfd75c9b360cf Mon Sep 17 00:00:00 2001 From: DebrisHauler Date: Sun, 19 Jan 2025 18:57:39 -0500 Subject: [PATCH 15/33] make format --- .../modes/games/bigbug/entityManager_bigbug.c | 33 +- main/modes/games/bigbug/entity_bigbug.c | 450 ++++++++++-------- main/modes/games/bigbug/entity_bigbug.h | 3 - main/modes/games/bigbug/mode_bigbug.c | 6 +- main/modes/games/bigbug/worldGen_bigbug.c | 16 +- 5 files changed, 267 insertions(+), 241 deletions(-) diff --git a/main/modes/games/bigbug/entityManager_bigbug.c b/main/modes/games/bigbug/entityManager_bigbug.c index b79dc3d25..d2418fa7e 100644 --- a/main/modes/games/bigbug/entityManager_bigbug.c +++ b/main/modes/games/bigbug/entityManager_bigbug.c @@ -237,7 +237,7 @@ void bb_loadSprites(bb_entityManager_t* entityManager) entityManager->sprites[BB_HOTDOG].brightnessLevels = 1; } - //final boss is unloaded until needed. + // final boss is unloaded until needed. entityManager->sprites[BB_FINAL_BOSS].originX = 30; entityManager->sprites[BB_FINAL_BOSS].originY = 40; } @@ -399,7 +399,7 @@ void bb_updateEntities(bb_entityManager_t* entityManager, bb_camera_t* camera) { curEntity->updateFarFunction(curEntity); } - else if(curEntity->spriteIndex == BB_GRABBY_HAND && !entityManager->sprites[BB_GRABBY_HAND].allocated) + else if (curEntity->spriteIndex == BB_GRABBY_HAND && !entityManager->sprites[BB_GRABBY_HAND].allocated) { bb_loadSprite("grab", 3, 1, &entityManager->sprites[BB_GRABBY_HAND]); curEntity->drawFunction = bb_drawGrabbyHand; @@ -867,7 +867,7 @@ bb_entity_t* bb_createEntity(bb_entityManager_t* entityManager, bb_animationType bb_entity_t* ovo = bb_createEntity(&entity->gameData->entityManager, NO_ANIMATION, true, OVO_TALK, 1, entity->gameData->camera.camera.pos.x, entity->gameData->camera.camera.pos.y, false, true); - if(ovo != NULL) + if (ovo != NULL) { bb_dialogueData_t* dData = bb_createDialogueData(11, "Ovo"); @@ -885,8 +885,9 @@ bb_entity_t* bb_createEntity(bb_entityManager_t* entityManager, bb_animationType "If your touch vector is outside of the purple circle that appears on-screen, harpoons " "will fire steadily."); bb_setCharacterLine(dData, 8, "Ovo", "Three hits will flip the bug upside down!"); - bb_setCharacterLine(dData, 9, "Ovo", - "There's a nifty detail where harpoons with upward velocity pass right through terrain."); + bb_setCharacterLine( + dData, 9, "Ovo", + "There's a nifty detail where harpoons with upward velocity pass right through terrain."); bb_setCharacterLine(dData, 10, "Ovo", "Now, let's get that bug!"); dData->curString = -1; @@ -1155,7 +1156,7 @@ bb_entity_t* bb_createEntity(bb_entityManager_t* entityManager, bb_animationType entityManager, LOOPING_ANIMATION, false, HARPOON, 3, (entity->pos.x >> DECIMAL_BITS) - 22, 0, false, false); // y position doesn't matter here. It will be handled in the update loop. - if(mData->cursor != NULL) + if (mData->cursor != NULL) { bb_clearCollisions(mData->cursor, false); @@ -1328,9 +1329,9 @@ bb_entity_t* bb_createEntity(bb_entityManager_t* entityManager, bb_animationType *collision = (bb_collision_t){others, bb_onCollisionGrabbyHand}; push(entity->collisions, (void*)collision); - entity->updateFunction = &bb_updateGrabbyHand; + entity->updateFunction = &bb_updateGrabbyHand; entity->updateFarFunction = &bb_updateFarGrabbyHand; - entity->drawFunction = &bb_drawGrabbyHand; + entity->drawFunction = &bb_drawGrabbyHand; // sprites loaded just-in-time bb_loadSprite("grab", 3, 1, &entityManager->sprites[BB_GRABBY_HAND]); @@ -1413,7 +1414,7 @@ bb_entity_t* bb_createEntity(bb_entityManager_t* entityManager, bb_animationType entity->gameData->camera.camera.pos.x, entity->gameData->camera.camera.pos.y, false, true); - if(ovo != NULL) + if (ovo != NULL) { bb_dialogueData_t* dData = bb_createDialogueData(3, "Ovo"); @@ -1507,7 +1508,7 @@ bb_entity_t* bb_createEntity(bb_entityManager_t* entityManager, bb_animationType } case BB_501KG: { - //just in time loading + // just in time loading bb_loadSprite("501kg", 1, 1, &entityManager->sprites[BB_501KG]); bb_setData(entity, heap_caps_calloc(1, sizeof(bb_501kgData_t), MALLOC_CAP_SPIRAM), BB_501KG_DATA); entity->updateFunction = &bb_update501kg; @@ -1677,17 +1678,17 @@ bb_entity_t* bb_createEntity(bb_entityManager_t* entityManager, bb_animationType case BB_QUICKPLAY_CONFIRM: { entity->updateFunction = &bb_updateQuickplay; - entity->drawFunction = &bb_drawQuickplay; + entity->drawFunction = &bb_drawQuickplay; break; } case BB_FINAL_BOSS: { bb_finalBossData_t* fbData = heap_caps_calloc(1, sizeof(bb_finalBossData_t), MALLOC_CAP_SPIRAM); - fbData->health = 5500; + fbData->health = 5500; bb_setData(entity, fbData, FINAL_BOSS_DATA); - - entity->halfWidth = 15 << DECIMAL_BITS; - entity->halfHeight = 15 << DECIMAL_BITS; + + entity->halfWidth = 15 << DECIMAL_BITS; + entity->halfHeight = 15 << DECIMAL_BITS; entity->collisions = heap_caps_calloc_tag(1, sizeof(list_t), MALLOC_CAP_SPIRAM, "rCollisions"); list_t* others = heap_caps_calloc_tag(1, sizeof(list_t), MALLOC_CAP_SPIRAM, "rOthers"); @@ -1704,7 +1705,7 @@ bb_entity_t* bb_createEntity(bb_entityManager_t* entityManager, bb_animationType *collision2 = (bb_collision_t){others2, bb_onCollisionBoss}; push(entity->collisions, (void*)collision2); - //just in time loading + // just in time loading bb_loadSprite("ovovoBoss", 8, 1, &entityManager->sprites[BB_FINAL_BOSS]); entity->updateFunction = &bb_updateFinalBoss; entity->drawFunction = &bb_drawFinalBoss; diff --git a/main/modes/games/bigbug/entity_bigbug.c b/main/modes/games/bigbug/entity_bigbug.c index e8600234d..eb4870e20 100644 --- a/main/modes/games/bigbug/entity_bigbug.c +++ b/main/modes/games/bigbug/entity_bigbug.c @@ -79,11 +79,13 @@ void bb_destroyEntity(bb_entity_t* self, bool caching, bool wasInTheMainArray) else if (self->spriteIndex == BB_FOOD_CART) { uint8_t thisFrame = self->currentAnimationFrame; - if(self->currentAnimationFrame > 1) + if (self->currentAnimationFrame > 1) { thisFrame = 1; } - if(caching && (self->gameData->entityManager.sprites[self->spriteIndex].frames[thisFrame].w || self->gameData->entityManager.sprites[self->spriteIndex].frames[thisFrame].h)) + if (caching + && (self->gameData->entityManager.sprites[self->spriteIndex].frames[thisFrame].w + || self->gameData->entityManager.sprites[self->spriteIndex].frames[thisFrame].h)) { freeWsg(&self->gameData->entityManager.sprites[self->spriteIndex].frames[thisFrame]); } @@ -337,7 +339,7 @@ void bb_updateRocketLiftoff(bb_entity_t* self) = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, OVO_TALK, 1, self->gameData->camera.camera.pos.x, self->gameData->camera.camera.pos.y, true, true); - if(ovo != NULL) + if (ovo != NULL) { bb_dialogueData_t* dData = bb_createDialogueData(1, "Ovo"); bb_setCharacterLine(dData, 0, "Ovo", "Time to check the loadout!"); @@ -498,7 +500,7 @@ void bb_updateGarbotnikDeploy(bb_entity_t* self) bb_entity_t* arm = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, ATTACHMENT_ARM, 1, self->pos.x >> DECIMAL_BITS, (self->pos.y >> DECIMAL_BITS) - 33, false, false); - if(arm != NULL) + if (arm != NULL) { ((bb_rocketData_t*)self->data)->armAngle = 2880; // That is 180 down position. ((bb_attachmentArmData_t*)arm->data)->rocket = self; @@ -508,7 +510,7 @@ void bb_updateGarbotnikDeploy(bb_entity_t* self) bb_entity_t* grabbyHand = bb_createEntity(&self->gameData->entityManager, LOOPING_ANIMATION, true, BB_GRABBY_HAND, 6, self->pos.x >> DECIMAL_BITS, (self->pos.y >> DECIMAL_BITS) - 53, true, false); - if(grabbyHand != NULL) + if (grabbyHand != NULL) { ((bb_grabbyHandData_t*)grabbyHand->data)->rocket = self; } @@ -520,7 +522,7 @@ void bb_updateGarbotnikDeploy(bb_entity_t* self) bb_entity_t* garbotnik = bb_createEntity(&(self->gameData->entityManager), NO_ANIMATION, true, GARBOTNIK_FLYING, 1, self->pos.x >> DECIMAL_BITS, (self->pos.y >> DECIMAL_BITS) - 50, true, false); - if(garbotnik != NULL) + if (garbotnik != NULL) { self->gameData->entityManager.viewEntity = garbotnik; self->updateFunction = bb_updateHeavyFalling; @@ -1169,7 +1171,7 @@ void bb_updateGarbotnikFlying(bb_entity_t* self) bb_entity_t* ovo = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, OVO_TALK, 1, self->gameData->camera.camera.pos.x, self->gameData->camera.camera.pos.y, false, true); - if(ovo != NULL) + if (ovo != NULL) { bb_dialogueData_t* dData = bb_createDialogueData(9, "Ovo"); @@ -1839,7 +1841,7 @@ void bb_updateMenu(bb_entity_t* self) &(self->gameData->entityManager), LOOPING_ANIMATION, false, FLAME_ANIM, 6, self->gameData->entityManager.boosterEntities[rocketIdx]->pos.x >> DECIMAL_BITS, self->gameData->entityManager.boosterEntities[rocketIdx]->pos.y >> DECIMAL_BITS, true, false); - if(rData->flame != NULL) + if (rData->flame != NULL) { rData->flame->updateFunction = &bb_updateFlame; } @@ -1854,15 +1856,15 @@ void bb_updateMenu(bb_entity_t* self) self->gameData->entityManager.deathDumpster = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, BB_DEATH_DUMPSTER, 1, self->pos.x >> DECIMAL_BITS, (self->pos.y >> DECIMAL_BITS) + 400, false, true); - if(self->gameData->entityManager.deathDumpster != NULL) + if (self->gameData->entityManager.deathDumpster != NULL) { - tData->tracking = self->gameData->entityManager.deathDumpster; - tData->midPointSqDist - = sqMagVec2d(divVec2d((vec_t){(tData->tracking->pos.x >> DECIMAL_BITS) - - (self->gameData->entityManager.viewEntity->pos.x >> DECIMAL_BITS), - (tData->tracking->pos.y >> DECIMAL_BITS) - - (self->gameData->entityManager.viewEntity->pos.y >> DECIMAL_BITS)}, - 2)); + tData->tracking = self->gameData->entityManager.deathDumpster; + tData->midPointSqDist = sqMagVec2d( + divVec2d((vec_t){(tData->tracking->pos.x >> DECIMAL_BITS) + - (self->gameData->entityManager.viewEntity->pos.x >> DECIMAL_BITS), + (tData->tracking->pos.y >> DECIMAL_BITS) + - (self->gameData->entityManager.viewEntity->pos.y >> DECIMAL_BITS)}, + 2)); } self->gameData->entityManager.viewEntity->updateFunction = &bb_updatePOI; @@ -1878,7 +1880,7 @@ void bb_updateMenu(bb_entity_t* self) self->pos.x >> DECIMAL_BITS, self->pos.y >> DECIMAL_BITS, true, true); } - self->gameData->btnState = 0; + self->gameData->btnState = 0; self->gameData->btnDownState = 0; } @@ -1912,7 +1914,7 @@ void bb_updateMenu(bb_entity_t* self) bb_entity_t* treadmillDust = bb_createEntity( &self->gameData->entityManager, NO_ANIMATION, true, NO_SPRITE_STAR, 1, (self->pos.x >> DECIMAL_BITS) + 140, (self->pos.y >> DECIMAL_BITS) - 165 + bb_randomInt(-10, 10), false, false); - if(treadmillDust != NULL) + if (treadmillDust != NULL) { treadmillDust->updateFunction = &bb_updateMoveLeft; } @@ -2044,10 +2046,10 @@ void bb_updateCharacterTalk(bb_entity_t* self) freeWsg(&dData->sprite); freeWsg(&dData->spriteNext); int8_t characterSprite = 0; - if (strcmp(dData->characters[dData->curString], "Ovo") == 0 || - strcmp(dData->characters[dData->curString], "???") == 0 || - strcmp(dData->characters[dData->curString], "Ovo???") == 0 || - strcmp(dData->characters[dData->curString], "DOCTOR OVO") == 0) + if (strcmp(dData->characters[dData->curString], "Ovo") == 0 + || strcmp(dData->characters[dData->curString], "???") == 0 + || strcmp(dData->characters[dData->curString], "Ovo???") == 0 + || strcmp(dData->characters[dData->curString], "DOCTOR OVO") == 0) { characterSprite = bb_randomInt(0, 6); loadWsgInplace("dialogue_next.wsg", &dData->spriteNext, true, bb_decodeSpace, bb_hsd); // TODO free @@ -2120,7 +2122,7 @@ void bb_updateAttachmentArm(bb_entity_t* self) rData->flame = bb_createEntity(&(self->gameData->entityManager), LOOPING_ANIMATION, false, FLAME_ANIM, 6, aData->rocket->pos.x >> DECIMAL_BITS, aData->rocket->pos.y >> DECIMAL_BITS, true, false); - if(rData->flame != NULL) + if (rData->flame != NULL) { rData->flame->updateFunction = &bb_updateFlame; aData->rocket->updateFunction = &bb_updateRocketLiftoff; @@ -2237,7 +2239,8 @@ void bb_updateGrabbyHand(bb_entity_t* self) self->pos.y = ghData->rocket->pos.y - 848; // that is 53 << 4 // retreat into the booster - if (self->gameData->entityManager.sprites[BB_GRABBY_HAND].originY > -26 && (ghData->grabbed == NULL || self->currentAnimationFrame == 2)) + if (self->gameData->entityManager.sprites[BB_GRABBY_HAND].originY > -26 + && (ghData->grabbed == NULL || self->currentAnimationFrame == 2)) { self->paused = true; self->gameData->entityManager.sprites[BB_GRABBY_HAND].originY -= 2; @@ -2248,7 +2251,8 @@ void bb_updateGrabbyHand(bb_entity_t* self) = self->pos.y - (self->gameData->entityManager.sprites[BB_GRABBY_HAND].originY << DECIMAL_BITS); } } - else if (self->gameData->entityManager.sprites[BB_GRABBY_HAND].originY <= -26 && ghData->grabbed != NULL && self->currentAnimationFrame == 2) + else if (self->gameData->entityManager.sprites[BB_GRABBY_HAND].originY <= -26 && ghData->grabbed != NULL + && self->currentAnimationFrame == 2) { self->currentAnimationFrame = 0; self->animationTimer = 0; @@ -2288,17 +2292,19 @@ void bb_updateGrabbyHand(bb_entity_t* self) bb_entity_t* ovo = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, OVO_TALK, 1, self->gameData->camera.camera.pos.x, self->gameData->camera.camera.pos.y, false, true); - if(ovo!=NULL) + if (ovo != NULL) { bb_dialogueData_t* dData = bb_createDialogueData(5, "Ovo"); bb_setCharacterLine(dData, 0, "Ovo", "One down, nine more to go!"); bb_setCharacterLine(dData, 1, "Ovo", "For every 10 bugs collected, the booster will emit a mega ping!"); - bb_setCharacterLine(dData, 2, "Ovo", - "When the mega ping is done, you can tune into some of the more important results."); - bb_setCharacterLine(dData, 3, "Ovo", - "Oh, you can pulse your own radar anytime with the start button. It takes some time to " - "interpret the pings."); + bb_setCharacterLine( + dData, 2, "Ovo", + "When the mega ping is done, you can tune into some of the more important results."); + bb_setCharacterLine( + dData, 3, "Ovo", + "Oh, you can pulse your own radar anytime with the start button. It takes some time to " + "interpret the pings."); bb_setCharacterLine(dData, 4, "Ovo", "So keep exploring while you wait."); dData->curString = -1; @@ -2317,7 +2323,7 @@ void bb_updateGrabbyHand(bb_entity_t* self) bb_entity_t* radarPing = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, BB_RADAR_PING, 1, self->pos.x >> DECIMAL_BITS, self->pos.y >> DECIMAL_BITS, true, false); - if(radarPing != NULL) + if (radarPing != NULL) { bb_radarPingData_t* rpData = (bb_radarPingData_t*)radarPing->data; rpData->color = c541; @@ -2336,7 +2342,7 @@ void bb_updateGrabbyHand(bb_entity_t* self) void bb_updateFarGrabbyHand(bb_entity_t* self) { - if(self->gameData->entityManager.sprites[BB_GRABBY_HAND].allocated) + if (self->gameData->entityManager.sprites[BB_GRABBY_HAND].allocated) { bb_freeSprite(&self->gameData->entityManager.sprites[BB_GRABBY_HAND]); self->drawFunction = bb_drawNothing; @@ -2491,15 +2497,15 @@ void bb_updatePangoAndFriends(bb_entity_t* self) bb_entity_t* ovo = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, OVO_TALK, 1, self->gameData->camera.camera.pos.x, self->gameData->camera.camera.pos.y, false, true); - if(ovo != NULL) + if (ovo != NULL) { if (self->gameData->day == 0) { bb_dialogueData_t* dData = bb_createDialogueData(18, "Pixel"); // longest possible string " " - // bb_setCharacterLine(dData, 0, "A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A - // A A A A A A A A A A A A A A A A A A A A A A A"); + // bb_setCharacterLine(dData, 0, "A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A + // A A A A A A A A A A A A A A A A A A A A A A A A A"); bb_setCharacterLine(dData, 0, "Pixel", "huff... huff... we actually caught up to it!"); bb_setCharacterLine(dData, 1, "Pixel", "ugfhh, my hair is starting to hurt. Can you make this quick, Pango?"); @@ -2554,12 +2560,14 @@ void bb_updatePangoAndFriends(bb_entity_t* self) bb_setCharacterLine(dData, 2, "Pango", "But MAGFast is literally named after me!"); bb_setCharacterLine(dData, 3, "Ovo", "You fool! It's called mag FEST because of PEST like you."); bb_setCharacterLine(dData, 4, "Po", "That's so metal."); - bb_setCharacterLine(dData, 5, "Ovo", - "POOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO" - "OOOOOOOOOOOOOOOOOOOO"); - bb_setCharacterLine(dData, 6, "Ovo", - "OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO" - "OOOOOOOOOOOOOOOOOOOOOOO!"); + bb_setCharacterLine( + dData, 5, "Ovo", + "POOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO" + "OOOOOOOOOOOOOOOOOOOO"); + bb_setCharacterLine( + dData, 6, "Ovo", + "OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO" + "OOOOOOOOOOOOOOOOOOOOOOO!"); bb_setCharacterLine(dData, 7, "Pixel", "Hey, don't yell at my friend like that!"); dData->curString = -1; @@ -2723,7 +2731,7 @@ void bb_update501kg(bb_entity_t* self) bb_entity_t* explosion = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, BB_EXPLOSION, 1, self->pos.x >> DECIMAL_BITS, self->pos.y >> DECIMAL_BITS, false, false); - if(explosion != NULL) + if (explosion != NULL) { bb_explosionData_t* eData = (bb_explosionData_t*)explosion->data; eData->radius = 180; @@ -3070,7 +3078,7 @@ void bb_updateSpaceLaser(bb_entity_t* self) void bb_updateQuickplay(bb_entity_t* self) { bb_menuData_t* mData; - for(int eIdx = 0; eIdx < MAX_ENTITIES; eIdx++) + for (int eIdx = 0; eIdx < MAX_ENTITIES; eIdx++) { bb_entity_t* menu = &self->gameData->entityManager.entities[eIdx]; if (menu != NULL && menu->spriteIndex == BB_MENU && menu->updateFunction != NULL) @@ -3094,14 +3102,14 @@ void bb_updateQuickplay(bb_entity_t* self) { if (mData->selectionIdx == 2) { - //no - //set selectionIdx to 1 + // no + // set selectionIdx to 1 mData->selectionIdx = 1; } - else if(mData->selectionIdx == 3) + else if (mData->selectionIdx == 3) { - //yes - //quick start the game + // yes + // quick start the game self->gameData->tutorialFlags = 255; uint32_t deathDumpsterX = (TILE_FIELD_WIDTH / 2) * TILE_SIZE + HALF_TILE - 1; @@ -3112,36 +3120,38 @@ void bb_updateQuickplay(bb_entity_t* self) { self->gameData->entityManager.boosterEntities[rocketIdx] = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, ROCKET_ANIM, 16, - deathDumpsterX - 96 + 96 * rocketIdx, deathDumpsterY, false, true); + deathDumpsterX - 96 + 96 * rocketIdx, deathDumpsterY, false, true); // bigbug->gameData.entityManager.boosterEntities[rocketIdx]->updateFunction = NULL; if (rocketIdx >= 1) { - bb_rocketData_t* rData = (bb_rocketData_t*)self->gameData->entityManager.boosterEntities[rocketIdx]->data; + bb_rocketData_t* rData + = (bb_rocketData_t*)self->gameData->entityManager.boosterEntities[rocketIdx]->data; bb_ensureEntitySpace(&self->gameData->entityManager, 1); rData->flame = bb_createEntity( &(self->gameData->entityManager), LOOPING_ANIMATION, false, FLAME_ANIM, 6, self->gameData->entityManager.boosterEntities[rocketIdx]->pos.x >> DECIMAL_BITS, self->gameData->entityManager.boosterEntities[rocketIdx]->pos.y >> DECIMAL_BITS, true, false); - if(rData->flame != NULL) + if (rData->flame != NULL) { rData->flame->updateFunction = &bb_updateFlame; } } else // rocketIdx == 0 { - self->gameData->entityManager.activeBooster = self->gameData->entityManager.boosterEntities[rocketIdx]; - self->gameData->entityManager.activeBooster->currentAnimationFrame = 40; - self->gameData->entityManager.activeBooster->pos.y = 50; - self->gameData->entityManager.activeBooster->updateFunction = bb_updateHeavyFalling; - + self->gameData->entityManager.activeBooster + = self->gameData->entityManager.boosterEntities[rocketIdx]; + self->gameData->entityManager.activeBooster->currentAnimationFrame = 40; + self->gameData->entityManager.activeBooster->pos.y = 50; + self->gameData->entityManager.activeBooster->updateFunction = bb_updateHeavyFalling; + bb_ensureEntitySpace(&self->gameData->entityManager, 1); - bb_entity_t* arm = bb_createEntity( + bb_entity_t* arm = bb_createEntity( &self->gameData->entityManager, NO_ANIMATION, true, ATTACHMENT_ARM, 1, self->gameData->entityManager.activeBooster->pos.x >> DECIMAL_BITS, (self->gameData->entityManager.activeBooster->pos.y >> DECIMAL_BITS) - 33, true, false); - if(arm != NULL) + if (arm != NULL) { ((bb_attachmentArmData_t*)arm->data)->rocket = self->gameData->entityManager.activeBooster; } @@ -3151,7 +3161,7 @@ void bb_updateQuickplay(bb_entity_t* self) &self->gameData->entityManager, LOOPING_ANIMATION, true, BB_GRABBY_HAND, 6, self->gameData->entityManager.activeBooster->pos.x >> DECIMAL_BITS, (self->gameData->entityManager.activeBooster->pos.y >> DECIMAL_BITS) - 53, true, false); - if(grabbyHand != NULL) + if (grabbyHand != NULL) { ((bb_grabbyHandData_t*)grabbyHand->data)->rocket = self->gameData->entityManager.activeBooster; } @@ -3160,18 +3170,18 @@ void bb_updateQuickplay(bb_entity_t* self) // create the death dumpster self->gameData->entityManager.deathDumpster - = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, BB_DEATH_DUMPSTER, 1, deathDumpsterX, - deathDumpsterY, false, true); + = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, BB_DEATH_DUMPSTER, 1, + deathDumpsterX, deathDumpsterY, false, true); // create garbotnik's UI bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, BB_GARBOTNIK_UI, 1, 0, 0, false, true); bb_ensureEntitySpace(&self->gameData->entityManager, 1); - self->gameData->entityManager.viewEntity - = bb_createEntity(&(self->gameData->entityManager), NO_ANIMATION, true, GARBOTNIK_FLYING, 1, - self->gameData->entityManager.activeBooster->pos.x >> DECIMAL_BITS, - (self->gameData->entityManager.activeBooster->pos.y >> DECIMAL_BITS) - 90, true, false); - if(self->gameData->entityManager.viewEntity != NULL) + self->gameData->entityManager.viewEntity = bb_createEntity( + &(self->gameData->entityManager), NO_ANIMATION, true, GARBOTNIK_FLYING, 1, + self->gameData->entityManager.activeBooster->pos.x >> DECIMAL_BITS, + (self->gameData->entityManager.activeBooster->pos.y >> DECIMAL_BITS) - 90, true, false); + if (self->gameData->entityManager.viewEntity != NULL) { self->gameData->entityManager.playerEntity = self->gameData->entityManager.viewEntity; } @@ -3198,7 +3208,6 @@ void bb_updateQuickplay(bb_entity_t* self) // ((bb_garbotnikData_t*)self->gameData->entityManager.playerEntity->data)->numHarpoons = 250; // self->gameData->GarbotnikStat_fireTime = 50; - // destroy self bb_destroyEntity(self, false, false); } @@ -3206,42 +3215,40 @@ void bb_updateQuickplay(bb_entity_t* self) void bb_updateFinalBoss(bb_entity_t* self) { - if(self->gameData->entityManager.playerEntity == NULL || FINAL_BOSS_DATA != self->dataType) + if (self->gameData->entityManager.playerEntity == NULL || FINAL_BOSS_DATA != self->dataType) { return; } bb_finalBossData_t* fbData = (bb_finalBossData_t*)self->data; - if(fbData == NULL) + if (fbData == NULL) { return; } - if(fbData->health <= 0) + if (fbData->health <= 0) { self->gameData->isPaused = true; - bb_entity_t* ovo = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, OVO_TALK, 1, - self->gameData->camera.camera.pos.x, - self->gameData->camera.camera.pos.y, false, true); - if(ovo != NULL) + bb_entity_t* ovo + = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, OVO_TALK, 1, + self->gameData->camera.camera.pos.x, self->gameData->camera.camera.pos.y, false, true); + if (ovo != NULL) { bb_dialogueData_t* dData = bb_createDialogueData(12, "Ovo"); bb_setCharacterLine(dData, 0, "Ovo", "Wow, I didn't think you would get this far."); - bb_setCharacterLine(dData, 1, "Ovo", "I'm about to grab the chaos orb, but I can see the future and the past now."); + bb_setCharacterLine(dData, 1, "Ovo", + "I'm about to grab the chaos orb, but I can see the future and the past now."); bb_setCharacterLine(dData, 2, "Ovo", "So here's how it goes down."); - bb_setCharacterLine(dData, 3, "Ovo", - "With the chaos orb, I boot up the time machine but it malfunctions!"); + bb_setCharacterLine(dData, 3, "Ovo", "With the chaos orb, I boot up the time machine but it malfunctions!"); bb_setCharacterLine( dData, 4, "Ovo", "Stuck in the carboniferous period, I have to fight off the the biggest bugs Earth has ever known."); - bb_setCharacterLine(dData, 5, "Ovo", - "I had to shatter the chaos orb to freeze time and save myself."); - bb_setCharacterLine(dData, 6, "Ovo", "And that's how all these big bugs ended up in the G.S.I. dump in the 21st century."); - bb_setCharacterLine(dData, 7, "Ovo", - "And the calendar doesn't work right past January 2025."); - bb_setCharacterLine(dData, 8, "Ovo", - "But that's a future problem."); + bb_setCharacterLine(dData, 5, "Ovo", "I had to shatter the chaos orb to freeze time and save myself."); + bb_setCharacterLine(dData, 6, "Ovo", + "And that's how all these big bugs ended up in the G.S.I. dump in the 21st century."); + bb_setCharacterLine(dData, 7, "Ovo", "And the calendar doesn't work right past January 2025."); + bb_setCharacterLine(dData, 8, "Ovo", "But that's a future problem."); bb_setCharacterLine(dData, 9, "Ovo", "It's all very confusing, I know."); bb_setCharacterLine(dData, 10, "Ovo", "Anyways, thanks for playing the game!"); bb_setCharacterLine(dData, 11, "Ovo", "This uiverse's timeline cannot process your awesomeness!"); @@ -3251,43 +3258,42 @@ void bb_updateFinalBoss(bb_entity_t* self) bb_setData(ovo, dData, DIALOGUE_DATA); } - //destroy self + // destroy self bb_destroyEntity(self, false, true); return; } - if(!fbData->firstDialogeDone) + if (!fbData->firstDialogeDone) { fbData->firstDialogeDone = true; self->gameData->isPaused = true; - bb_entity_t* ovo = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, OVO_TALK, 1, - self->gameData->camera.camera.pos.x, - self->gameData->camera.camera.pos.y, false, true); - if(ovo != NULL) + bb_entity_t* ovo + = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, OVO_TALK, 1, + self->gameData->camera.camera.pos.x, self->gameData->camera.camera.pos.y, false, true); + if (ovo != NULL) { bb_dialogueData_t* dData = bb_createDialogueData(6, "???"); bb_setCharacterLine(dData, 0, "???", "muahahahahaha!"); bb_setCharacterLine(dData, 1, "Ovo", "Who's there?"); bb_setCharacterLine(dData, 2, "???", "..."); - bb_setCharacterLine(dData, 3, "Ovo", - "Who the heck else is down here?"); - bb_setCharacterLine(dData, 4, "???", "..."); + bb_setCharacterLine(dData, 3, "Ovo", "Who the heck else is down here?"); + bb_setCharacterLine(dData, 4, "???", "..."); bb_setCharacterLine(dData, 5, "???", "Right. On. Time."); dData->endDialogueCB = &bb_afterGarbotnikTutorialTalk; dData->curString = -1; - bb_setData(ovo, dData, DIALOGUE_DATA); + bb_setData(ovo, dData, DIALOGUE_DATA); } } - else if(!fbData->secondDialogeDone && fbData->health < 3500) + else if (!fbData->secondDialogeDone && fbData->health < 3500) { fbData->secondDialogeDone = true; - self->gameData->isPaused = true; - bb_entity_t* ovo = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, OVO_TALK, 1, - self->gameData->camera.camera.pos.x, - self->gameData->camera.camera.pos.y, false, true); - if(ovo != NULL) + self->gameData->isPaused = true; + bb_entity_t* ovo + = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, OVO_TALK, 1, + self->gameData->camera.camera.pos.x, self->gameData->camera.camera.pos.y, false, true); + if (ovo != NULL) { bb_dialogueData_t* dData = bb_createDialogueData(16, "Ovo"); @@ -3295,17 +3301,19 @@ void bb_updateFinalBoss(bb_entity_t* self) bb_setCharacterLine(dData, 1, "Ovo???", "HA! I'm future you from the past!"); bb_setCharacterLine(dData, 2, "Ovo", "How is this possible?"); bb_setCharacterLine(dData, 3, "Ovo???", - "Do you think Garbotnik stays in heaven because he, too, lives in fear of what he's created here on earth?"); - bb_setCharacterLine(dData, 4, "Ovo", "That actually makes no sense. "); + "Do you think Garbotnik stays in heaven because he, too, lives in fear of what he's " + "created here on earth?"); + bb_setCharacterLine(dData, 4, "Ovo", "That actually makes no sense. "); bb_setCharacterLine(dData, 5, "Ovo???", "How could I expect YOU to understand?"); bb_setCharacterLine(dData, 6, "DOCTOR OVO", "You're stuck living in the present, Ovo. And I have a PHD!"); - bb_setCharacterLine(dData, 7, "Ovo", "Oh nice! What will my degree be in?"); + bb_setCharacterLine(dData, 7, "Ovo", "Oh nice! What will my degree be in?"); bb_setCharacterLine(dData, 8, "DOCTOR OVO", "How naive!"); bb_setCharacterLine(dData, 9, "DOCTOR OVO", "There are no fancy titles in death, fool."); bb_setCharacterLine(dData, 10, "Ovo", "You watch your mouth! Because I'm the real O.G.!"); bb_setCharacterLine(dData, 11, "Ovo", "WHERE'S MY CHAOS ORB?"); char voiceLine[30]; - snprintf(voiceLine, sizeof(voiceLine), "You're %d day%s too late.", self->gameData->day, self->gameData->day == 1 ? "" : "s"); + snprintf(voiceLine, sizeof(voiceLine), "You're %d day%s too late.", self->gameData->day, + self->gameData->day == 1 ? "" : "s"); bb_setCharacterLine(dData, 12, "DOCTOR OVO", voiceLine); bb_setCharacterLine(dData, 13, "DOCTOR OVO", "Now there's just one loose end I have to tie up."); bb_setCharacterLine(dData, 14, "DOCTOR OVO", "AND IT'S YOU!"); @@ -3313,7 +3321,7 @@ void bb_updateFinalBoss(bb_entity_t* self) dData->endDialogueCB = &bb_afterGarbotnikTutorialTalk; dData->curString = -1; - bb_setData(ovo, dData, DIALOGUE_DATA); + bb_setData(ovo, dData, DIALOGUE_DATA); } } @@ -3322,56 +3330,60 @@ void bb_updateFinalBoss(bb_entity_t* self) fbData->damageEffect -= self->gameData->elapsedUs >> 11; } - //get a normalized vector from self to the player + // get a normalized vector from self to the player vec_t toPlayer = subVec2d(self->gameData->entityManager.playerEntity->pos, self->pos); fastNormVec(&toPlayer.x, &toPlayer.y); fbData->vel = addVec2d(fbData->vel, mulVec2d(toPlayer, self->gameData->elapsedUs >> 14)); - //apply drag to velocity + // apply drag to velocity fbData->vel = divVec2d(fbData->vel, 2); - //move the boss - self->pos = addVec2d(self->pos, divVec2d(mulVec2d(fbData->vel, self->gameData->elapsedUs >> 14),16)); + // move the boss + self->pos = addVec2d(self->pos, divVec2d(mulVec2d(fbData->vel, self->gameData->elapsedUs >> 14), 16)); - for(int egg = 0; egg < 10; egg++) + for (int egg = 0; egg < 10; egg++) { - if(fbData->bossEggs[egg] != NULL && fbData->bossEggs[egg]->active && EGG_LEAVES_DATA == fbData->bossEggs[egg]->dataType) + if (fbData->bossEggs[egg] != NULL && fbData->bossEggs[egg]->active + && EGG_LEAVES_DATA == fbData->bossEggs[egg]->dataType) { bb_eggLeavesData_t* elData = (bb_eggLeavesData_t*)fbData->bossEggs[egg]->data; - //calculate new egg pos relative to self. + // calculate new egg pos relative to self. vec_t toEgg = subVec2d(fbData->bossEggs[egg]->pos, self->pos); - toEgg = rotateVec2d(toEgg, 1); + toEgg = rotateVec2d(toEgg, 1); fastNormVec(&toEgg.x, &toEgg.y); - toEgg = mulVec2d(toEgg, 5); + toEgg = mulVec2d(toEgg, 5); fbData->bossEggs[egg]->pos = addVec2d(self->pos, toEgg); - elData->egg->pos = fbData->bossEggs[egg]->pos; + elData->egg->pos = fbData->bossEggs[egg]->pos; } } - //increment the stateTimer + // increment the stateTimer fbData->stateTimer += self->gameData->elapsedUs >> 9; - if(fbData->stateTimer > 0) + if (fbData->stateTimer > 0) { uint8_t check = fbData->stateTimer / 3277; - if(fbData->bossEggs[check] == NULL || fbData->bossEggs[check]->active == false || EGG_LEAVES_DATA != fbData->bossEggs[check]->dataType) + if (fbData->bossEggs[check] == NULL || fbData->bossEggs[check]->active == false + || EGG_LEAVES_DATA != fbData->bossEggs[check]->dataType) { bb_ensureEntitySpace(&self->gameData->entityManager, 2); - fbData->bossEggs[check] + fbData->bossEggs[check] = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, EGG_LEAVES, 1, - self->pos.x >> DECIMAL_BITS, self->pos.y >> DECIMAL_BITS, false, false); + self->pos.x >> DECIMAL_BITS, self->pos.y >> DECIMAL_BITS, false, false); if (fbData->bossEggs[check] != NULL) { - ((bb_eggLeavesData_t*)fbData->bossEggs[check]->data)->egg = bb_createEntity( - &self->gameData->entityManager, NO_ANIMATION, true, EGG, 1, (fbData->bossEggs[check]->pos.x >> DECIMAL_BITS) - 200 + bb_randomInt(0, 400), - 195 << 5, false, false); + ((bb_eggLeavesData_t*)fbData->bossEggs[check]->data)->egg + = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, EGG, 1, + (fbData->bossEggs[check]->pos.x >> DECIMAL_BITS) - 200 + bb_randomInt(0, 400), + 195 << 5, false, false); if (((bb_eggLeavesData_t*)fbData->bossEggs[check]->data)->egg == NULL) { bb_destroyEntity(fbData->bossEggs[check], false, true); } else { - fbData->bossEggs[check] ->collisions = heap_caps_calloc_tag(1, sizeof(list_t), MALLOC_CAP_SPIRAM, "rCollisions"); - list_t* others = heap_caps_calloc_tag(1, sizeof(list_t), MALLOC_CAP_SPIRAM, "rOthers"); + fbData->bossEggs[check]->collisions + = heap_caps_calloc_tag(1, sizeof(list_t), MALLOC_CAP_SPIRAM, "rCollisions"); + list_t* others = heap_caps_calloc_tag(1, sizeof(list_t), MALLOC_CAP_SPIRAM, "rOthers"); push(others, (void*)GARBOTNIK_FLYING); bb_collision_t* collision = heap_caps_calloc_tag(1, sizeof(bb_collision_t), MALLOC_CAP_SPIRAM, "rCollision"); @@ -3792,9 +3804,9 @@ void bb_drawCharacterTalk(bb_entityManager_t* entityManager, rectangle_t* camera { textColor = c544; } - else if (strcmp(dData->characters[dData->curString], "???") == 0 || - strcmp(dData->characters[dData->curString], "Ovo???") == 0 || - strcmp(dData->characters[dData->curString], "DOCTOR OVO") == 0) + else if (strcmp(dData->characters[dData->curString], "???") == 0 + || strcmp(dData->characters[dData->curString], "Ovo???") == 0 + || strcmp(dData->characters[dData->curString], "DOCTOR OVO") == 0) { textColor = c550; } @@ -4479,7 +4491,7 @@ void bb_drawDeadBug(bb_entityManager_t* entityManager, rectangle_t* camera, bb_e void bb_drawQuickplay(bb_entityManager_t* entityManager, rectangle_t* camera, bb_entity_t* self) { bb_menuData_t* mData; - for(int eIdx = 0; eIdx < MAX_ENTITIES; eIdx++) + for (int eIdx = 0; eIdx < MAX_ENTITIES; eIdx++) { bb_entity_t* menu = &entityManager->entities[eIdx]; if (menu != NULL && menu->spriteIndex == BB_MENU && menu->updateFunction != NULL) @@ -4493,15 +4505,22 @@ void bb_drawQuickplay(bb_entityManager_t* entityManager, rectangle_t* camera, bb int16_t xOff = 20; int16_t yOff = 30; - drawTextWordWrapCentered(&self->gameData->font, c444, "Quick Play mode skips the intro sequence and all tutorial dialogue. Are you sure you want to continue?", &xOff, &yOff, TFT_WIDTH - 5, TFT_HEIGHT - 5); - drawRect((TFT_WIDTH >> 2) - 40, TFT_HEIGHT >> 1, (TFT_WIDTH >> 2) + 40, (TFT_HEIGHT >> 1) + 40, mData->selectionIdx == 2 ? c550 : c444); + drawTextWordWrapCentered( + &self->gameData->font, c444, + "Quick Play mode skips the intro sequence and all tutorial dialogue. Are you sure you want to continue?", &xOff, + &yOff, TFT_WIDTH - 5, TFT_HEIGHT - 5); + drawRect((TFT_WIDTH >> 2) - 40, TFT_HEIGHT >> 1, (TFT_WIDTH >> 2) + 40, (TFT_HEIGHT >> 1) + 40, + mData->selectionIdx == 2 ? c550 : c444); xOff = (TFT_WIDTH >> 2) - 40; yOff = (TFT_HEIGHT >> 1) + 15; - drawTextWordWrapCentered(&self->gameData->font, mData->selectionIdx == 2 ? c550 : c444, "NO", &xOff, &yOff, (TFT_WIDTH >> 2) + 40, (TFT_HEIGHT >> 1) + 60); - drawRect((TFT_WIDTH >> 1) + (TFT_WIDTH >> 2) - 40, TFT_HEIGHT >> 1, (TFT_WIDTH >> 1) + (TFT_WIDTH >> 2) + 40, (TFT_HEIGHT >> 1) + 40, mData->selectionIdx == 3 ? c550 : c444); + drawTextWordWrapCentered(&self->gameData->font, mData->selectionIdx == 2 ? c550 : c444, "NO", &xOff, &yOff, + (TFT_WIDTH >> 2) + 40, (TFT_HEIGHT >> 1) + 60); + drawRect((TFT_WIDTH >> 1) + (TFT_WIDTH >> 2) - 40, TFT_HEIGHT >> 1, (TFT_WIDTH >> 1) + (TFT_WIDTH >> 2) + 40, + (TFT_HEIGHT >> 1) + 40, mData->selectionIdx == 3 ? c550 : c444); xOff = (TFT_WIDTH >> 1) + (TFT_WIDTH >> 2) - 40; yOff = (TFT_HEIGHT >> 1) + 15; - drawTextWordWrapCentered(&self->gameData->font, mData->selectionIdx == 3 ? c550 : c444, "YES", &xOff, &yOff, (TFT_WIDTH >> 1) + (TFT_WIDTH >> 2) + 40, (TFT_HEIGHT >> 1) + 60); + drawTextWordWrapCentered(&self->gameData->font, mData->selectionIdx == 3 ? c550 : c444, "YES", &xOff, &yOff, + (TFT_WIDTH >> 1) + (TFT_WIDTH >> 2) + 40, (TFT_HEIGHT >> 1) + 60); } void bb_drawFinalBoss(bb_entityManager_t* entityManager, rectangle_t* camera, bb_entity_t* self) @@ -4514,13 +4533,14 @@ void bb_drawFinalBoss(bb_entityManager_t* entityManager, rectangle_t* camera, bb bb_finalBossData_t* fbData = (bb_finalBossData_t*)self->data; // draw the tow cables - for(int egg = 0; egg < 10; egg++) + for (int egg = 0; egg < 10; egg++) { - if(fbData->bossEggs[egg] != NULL && fbData->bossEggs[egg]->active && EGG_LEAVES_DATA == fbData->bossEggs[egg]->dataType) + if (fbData->bossEggs[egg] != NULL && fbData->bossEggs[egg]->active + && EGG_LEAVES_DATA == fbData->bossEggs[egg]->dataType) { drawLineFast((self->pos.x >> DECIMAL_BITS) - camera->pos.x, (self->pos.y >> DECIMAL_BITS) - camera->pos.y, - (fbData->bossEggs[egg]->pos.x >> DECIMAL_BITS) - camera->pos.x, - (fbData->bossEggs[egg]->pos.y >> DECIMAL_BITS) - camera->pos.y, c425); + (fbData->bossEggs[egg]->pos.x >> DECIMAL_BITS) - camera->pos.x, + (fbData->bossEggs[egg]->pos.y >> DECIMAL_BITS) - camera->pos.y, c425); } } // node_t* current = gData->towedEntities.first; @@ -4544,10 +4564,9 @@ void bb_drawFinalBoss(bb_entityManager_t* entityManager, rectangle_t* camera, bb } else { - drawWsg(&entityManager->sprites[self->spriteIndex].frames[self->currentAnimationFrame], xOff, yOff, fbData->vel.x > 0, false, 0); + drawWsg(&entityManager->sprites[self->spriteIndex].frames[self->currentAnimationFrame], xOff, yOff, + fbData->vel.x > 0, false, 0); } - - } // void bb_drawRect(bb_entityManager_t* entityManager, rectangle_t* camera, bb_entity_t* self) @@ -4566,12 +4585,13 @@ void bb_onCollisionHarpoon(bb_entity_t* self, bb_entity_t* other, bb_hitInfo_t* // pop that egg int32_t tile_i = other->pos.x >> 9; // 4 decimal bits and 5 bitshifts is divide by 32. int32_t tile_j = other->pos.y >> 9; // 4 decimal bits and 5 bitshifts is divide by 32. - if(self->gameData->tilemap.fgTiles[tile_i][tile_j].health == 0)//This case is for boss eggs that are in the air. + if (self->gameData->tilemap.fgTiles[tile_i][tile_j].health + == 0) // This case is for boss eggs that are in the air. { - //spawn a bug + // spawn a bug bb_ensureEntitySpace(&self->gameData->entityManager, 1); bb_entity_t* bug = bb_createEntity(&self->gameData->entityManager, LOOPING_ANIMATION, false, - bb_randomInt(8, 13), 1, tile_i<<5, tile_j<<5, false, false); + bb_randomInt(8, 13), 1, tile_i << 5, tile_j << 5, false, false); if (bug != NULL) { @@ -4624,7 +4644,7 @@ void bb_onCollisionHarpoon(bb_entity_t* self, bb_entity_t* other, bb_hitInfo_t* bb_entity_t* ovo = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, OVO_TALK, 1, self->gameData->camera.camera.pos.x, self->gameData->camera.camera.pos.y, false, true); - if(ovo != NULL) + if (ovo != NULL) { bb_dialogueData_t* dData = bb_createDialogueData(7, "Ovo"); @@ -4644,7 +4664,7 @@ void bb_onCollisionHarpoon(bb_entity_t* self, bb_entity_t* other, bb_hitInfo_t* dData->curString = -1; dData->endDialogueCB = &bb_afterGarbotnikTutorialTalk; - bb_setData(ovo, dData, DIALOGUE_DATA); + bb_setData(ovo, dData, DIALOGUE_DATA); } } } @@ -5130,7 +5150,7 @@ void bb_onCollisionBrickTutorial(bb_entity_t* self, bb_entity_t* other, bb_hitIn bb_entity_t* ovo = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, OVO_TALK, 1, self->gameData->camera.camera.pos.x, self->gameData->camera.camera.pos.y, false, true); - if(ovo != NULL) + if (ovo != NULL) { bb_dialogueData_t* dData = bb_createDialogueData(12, "Ovo"); @@ -5145,7 +5165,8 @@ void bb_onCollisionBrickTutorial(bb_entity_t* self, bb_entity_t* other, bb_hitIn dData, 6, "Ovo", "\"Same as the other garbage densities, bricks will crumble if they are totally disconnected.\""); bb_setCharacterLine(dData, 7, "Ovo", "\"Or they can be smashed easily with heavy falling objects.\""); - bb_setCharacterLine(dData, 8, "Ovo", "\"Big Bug's early-game design is all about using your fuel to get upgrades.\""); + bb_setCharacterLine(dData, 8, "Ovo", + "\"Big Bug's early-game design is all about using your fuel to get upgrades.\""); bb_setCharacterLine(dData, 9, "Ovo", "\"So be sure to strategize how to use your time!\""); bb_setCharacterLine(dData, 10, "Ovo", "\"The late-game design has a shift in focus away from fuel.\""); bb_setCharacterLine(dData, 11, "Ovo", "\"You'll just have to see it for yourself!\""); @@ -5206,7 +5227,7 @@ void bb_onCollisionBossEgg(bb_entity_t* self, bb_entity_t* other, bb_hitInfo_t* bb_ensureEntitySpace(&self->gameData->entityManager, 1); bb_entity_t* bug = bb_createEntity(&self->gameData->entityManager, LOOPING_ANIMATION, false, bb_randomInt(8, 13), 1, - elData->egg->pos.x >> DECIMAL_BITS, elData->egg->pos.y >> DECIMAL_BITS, false, false); + elData->egg->pos.x >> DECIMAL_BITS, elData->egg->pos.y >> DECIMAL_BITS, false, false); if (bug != NULL) { // destroy the egg @@ -5218,12 +5239,12 @@ void bb_onCollisionBossEgg(bb_entity_t* self, bb_entity_t* other, bb_hitInfo_t* void bb_onCollisionBoss(bb_entity_t* self, bb_entity_t* other, bb_hitInfo_t* hitInfo) { - //damage self + // damage self bb_finalBossData_t* fbData = (bb_finalBossData_t*)self->data; - fbData->damageEffect = 100; + fbData->damageEffect = 100; fbData->health -= 34; - //destroy other + // destroy other bb_destroyEntity(other, false, true); } @@ -5232,23 +5253,24 @@ void bb_startGarbotnikIntro(bb_entity_t* self) bb_entity_t* ovo = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, OVO_TALK, 1, self->gameData->camera.camera.pos.x, self->gameData->camera.camera.pos.y, false, true); - if(ovo != NULL) + if (ovo != NULL) { bb_dialogueData_t* dData = bb_createDialogueData(24, "Ovo"); // longest possible string " " - // bb_setCharacterLine(dData, 0, "A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A - // A A A A A A A A A A A A A A A A A A A"); + // bb_setCharacterLine(dData, 0, "A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A + // A A A A A A A A A A A A A A A A A A A A A"); bb_setCharacterLine(dData, 0, "Ovo", "Holy bug farts!"); // bb_setCharacterLine( dData, 1, "Ovo", "After I sold the chilidog car fresheners to MAGFest, Garbotnik Industries' stock went up by 6,969%!"); - bb_setCharacterLine(dData, 2, "Ovo", - "I'm going to use my time machine to steal the next big-selling trinket from the future now."); - bb_setCharacterLine(dData, 3, "Ovo", "That will floor all my stakeholders and make me UNDEFINED money!"); bb_setCharacterLine( - dData, 4, "Ovo", - "With that kind of cash, I can recruit 200 professional bassoon players to the MAGFest Community Orchestra."); + dData, 2, "Ovo", + "I'm going to use my time machine to steal the next big-selling trinket from the future now."); + bb_setCharacterLine(dData, 3, "Ovo", "That will floor all my stakeholders and make me UNDEFINED money!"); + bb_setCharacterLine(dData, 4, "Ovo", + "With that kind of cash, I can recruit 200 professional bassoon players to the MAGFest " + "Community Orchestra."); bb_setCharacterLine(dData, 5, "Ovo", "I'm so hyped to turn on my time machine for the first time!"); bb_setCharacterLine(dData, 6, "Ovo", "Everything's in order."); bb_setCharacterLine(dData, 7, "Ovo", "Even Pango can't stop me!"); @@ -5262,9 +5284,10 @@ void bb_startGarbotnikIntro(bb_entity_t* self) bb_setCharacterLine(dData, 15, "Ovo", "Can I get an F in the chat?"); bb_setCharacterLine(dData, 16, "Ovo", "..."); bb_setCharacterLine(dData, 17, "Ovo", "The chaos orb is three times denser than a black hole."); - bb_setCharacterLine(dData, 18, "Ovo", - "Well if Garbotnik Sanitation Industries took it to the landfill, then it is definitely at the " - "VERY BOTTOM of the dump."); + bb_setCharacterLine( + dData, 18, "Ovo", + "Well if Garbotnik Sanitation Industries took it to the landfill, then it is definitely at the " + "VERY BOTTOM of the dump."); bb_setCharacterLine(dData, 19, "Ovo", "Not a problem."); bb_setCharacterLine(dData, 20, "Ovo", "We have the technology to retrieve it."); bb_setCharacterLine( @@ -5277,13 +5300,13 @@ void bb_startGarbotnikIntro(bb_entity_t* self) dData->endDialogueCB = &bb_afterGarbotnikIntro; - bb_setData(ovo, dData, DIALOGUE_DATA); + bb_setData(ovo, dData, DIALOGUE_DATA); } } void bb_startGarbotnikLandingTalk(bb_entity_t* self) { - if(self == NULL) + if (self == NULL) { return; } @@ -5292,7 +5315,7 @@ void bb_startGarbotnikLandingTalk(bb_entity_t* self) bb_entity_t* ovo = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, OVO_TALK, 1, self->gameData->camera.camera.pos.x, self->gameData->camera.camera.pos.y, false, true); - if(ovo != NULL) + if (ovo != NULL) { bb_dialogueData_t* dData = bb_createDialogueData(1, "Ovo"); @@ -5372,7 +5395,8 @@ void bb_startGarbotnikLandingTalk(bb_entity_t* self) } case 9: { - bb_setCharacterLine(dData, 0, "Ovo", "Is it spelled \"moustache\" or \"mustache?\" Better look it up..."); + bb_setCharacterLine(dData, 0, "Ovo", + "Is it spelled \"moustache\" or \"mustache?\" Better look it up..."); break; } case 10: @@ -5392,15 +5416,17 @@ void bb_startGarbotnikLandingTalk(bb_entity_t* self) } case 13: { - bb_setCharacterLine(dData, 0, "Ovo", - "I must remember to research what dastardly technology this dump used to ensure new " - "arrivals wind up at the bottom..."); + bb_setCharacterLine( + dData, 0, "Ovo", + "I must remember to research what dastardly technology this dump used to ensure new " + "arrivals wind up at the bottom..."); break; } case 14: { - bb_setCharacterLine(dData, 0, "Ovo", - "I promise, this is perfectly sanitary, the Chaos Orb has antimicrobial properties."); + bb_setCharacterLine( + dData, 0, "Ovo", + "I promise, this is perfectly sanitary, the Chaos Orb has antimicrobial properties."); break; } case 15: @@ -5420,9 +5446,9 @@ void bb_startGarbotnikLandingTalk(bb_entity_t* self) } case 18: { - bb_setCharacterLine( - dData, 0, "Ovo", - "If you ask me what I have a degree in one more time, I'm asking the Internet to draw fanart of you."); + bb_setCharacterLine(dData, 0, "Ovo", + "If you ask me what I have a degree in one more time, I'm asking the Internet to " + "draw fanart of you."); break; } case 19: @@ -5432,7 +5458,8 @@ void bb_startGarbotnikLandingTalk(bb_entity_t* self) } case 20: { - bb_setCharacterLine(dData, 0, "Ovo", "Did I remember to turn off the disintegrator before I left the lab?"); + bb_setCharacterLine(dData, 0, "Ovo", + "Did I remember to turn off the disintegrator before I left the lab?"); break; } case 21: @@ -5468,7 +5495,8 @@ void bb_startGarbotnikLandingTalk(bb_entity_t* self) } case 27: { - bb_setCharacterLine(dData, 0, "Ovo", "Remember, Ovo is pronounced like 'oooh-voo' with a wink at the end."); + bb_setCharacterLine(dData, 0, "Ovo", + "Remember, Ovo is pronounced like 'oooh-voo' with a wink at the end."); break; } case 28: @@ -5493,7 +5521,7 @@ void bb_startGarbotnikCloningTalk(bb_entity_t* self) { bb_entity_t* ovo = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, OVO_TALK, 1, 0, 0, false, true); - if(ovo != NULL) + if (ovo != NULL) { bb_dialogueData_t* dData = bb_createDialogueData(2, "Ovo"); // 29 @@ -5514,11 +5542,11 @@ void bb_startGarbotnikEggTutorialTalk(bb_entity_t* self) bb_entity_t* ovo = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, OVO_TALK, 1, self->gameData->camera.camera.pos.x, self->gameData->camera.camera.pos.y, false, true); - if(ovo != NULL) + if (ovo != NULL) { bb_dialogueData_t* dData = bb_createDialogueData(2, "Ovo"); - // Max dialogue string roughly: here----V + // Max dialogue string roughly: here----V bb_setCharacterLine(dData, 0, "Ovo", "Oooey Gooey! Look at that egg sack!"); bb_setCharacterLine(dData, 1, "Ovo", "I can use the directional buttons on my swadge to fly over there and check it out."); @@ -5538,17 +5566,19 @@ void bb_startGarbotnikFuelTutorialTalk(bb_entity_t* self) bb_entity_t* ovo = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, OVO_TALK, 1, self->gameData->camera.camera.pos.x, self->gameData->camera.camera.pos.y, false, true); - if(ovo != NULL) + if (ovo != NULL) { bb_dialogueData_t* dData = bb_createDialogueData(5, "Ovo"); // longest possible string " " - // bb_setCharacterLine(dData, 0, "A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A - // A A A A A A A A A A A A A A A A A A A"); - bb_setCharacterLine(dData, 0, "Ovo", - "When I travel away from the booster, I've got to keep an eye on my fuel level at all times."); - bb_setCharacterLine(dData, 1, "Ovo", - "Sit back atop the booster before all the lights around the outside of the swadge turn off."); + // bb_setCharacterLine(dData, 0, "A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A + // A A A A A A A A A A A A A A A A A A A A A"); + bb_setCharacterLine( + dData, 0, "Ovo", + "When I travel away from the booster, I've got to keep an eye on my fuel level at all times."); + bb_setCharacterLine( + dData, 1, "Ovo", + "Sit back atop the booster before all the lights around the outside of the swadge turn off."); bb_setCharacterLine(dData, 2, "Ovo", "safety first. Once back on the rocket, it takes a stupid long time for the " "launch sequence to initiate."); @@ -5623,7 +5653,7 @@ void bb_afterGarbotnikLandingTalk(bb_entity_t* self) bb_entity_t* ovo = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, OVO_TALK, 1, self->gameData->camera.camera.pos.x, self->gameData->camera.camera.pos.y, false, true); - if(ovo != NULL) + if (ovo != NULL) { bb_dialogueData_t* dData = bb_createDialogueData(2, "Ovo"); @@ -5663,17 +5693,17 @@ void bb_afterGarbotnikLandingTalk(bb_entity_t* self) = bb_createEntity(&(self->gameData->entityManager), NO_ANIMATION, true, NO_SPRITE_POI, 1, (self->gameData->entityManager.playerEntity->pos.x >> DECIMAL_BITS), (self->gameData->entityManager.playerEntity->pos.y >> DECIMAL_BITS), false, false); - if(self->gameData->entityManager.viewEntity != NULL) + if (self->gameData->entityManager.viewEntity != NULL) { bb_goToData* tData = (bb_goToData*)self->gameData->entityManager.viewEntity->data; tData->tracking = curEntity; - tData->midPointSqDist - = sqMagVec2d(divVec2d((vec_t){(tData->tracking->pos.x >> DECIMAL_BITS) - - (self->gameData->entityManager.viewEntity->pos.x >> DECIMAL_BITS), - (tData->tracking->pos.y >> DECIMAL_BITS) - - (self->gameData->entityManager.viewEntity->pos.y >> DECIMAL_BITS)}, - 2)); + tData->midPointSqDist = sqMagVec2d( + divVec2d((vec_t){(tData->tracking->pos.x >> DECIMAL_BITS) + - (self->gameData->entityManager.viewEntity->pos.x >> DECIMAL_BITS), + (tData->tracking->pos.y >> DECIMAL_BITS) + - (self->gameData->entityManager.viewEntity->pos.y >> DECIMAL_BITS)}, + 2)); tData->executeOnArrival = &bb_startGarbotnikEggTutorialTalk; self->gameData->entityManager.viewEntity->updateFunction = &bb_updatePOI; } @@ -5898,7 +5928,7 @@ void bb_bugDeath(bb_entity_t* self, bb_hitInfo_t* hitInfo) bb_entity_t* hitEffect = bb_createEntity(&(self->gameData->entityManager), ONESHOT_ANIMATION, false, BUMP_ANIM, 6, hitInfo->pos.x >> DECIMAL_BITS, hitInfo->pos.y >> DECIMAL_BITS, true, false); - if(hitEffect != NULL) + if (hitEffect != NULL) { hitEffect->drawFunction = &bb_drawHitEffect; } @@ -5968,7 +5998,7 @@ void bb_cartDeath(bb_entity_t* self, bb_hitInfo_t* hitInfo) bb_entity_t* hitEffect = bb_createEntity(&(self->gameData->entityManager), ONESHOT_ANIMATION, false, BUMP_ANIM, 6, hitInfo->pos.x >> DECIMAL_BITS, hitInfo->pos.y >> DECIMAL_BITS, true, false); - if(hitEffect != NULL) + if (hitEffect != NULL) { hitEffect->drawFunction = &bb_drawHitEffect; } @@ -6133,9 +6163,9 @@ void bb_afterEnding(bb_entity_t* self) void bb_trigger501kg(bb_entity_t* self) { bb_ensureEntitySpace(&self->gameData->entityManager, 1); - bb_entity_t* missile = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, BB_501KG, 1, - self->pos.x >> DECIMAL_BITS, self->pos.y >> DECIMAL_BITS, true, false); - if(missile != NULL) + bb_entity_t* missile = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, BB_501KG, 1, + self->pos.x >> DECIMAL_BITS, self->pos.y >> DECIMAL_BITS, true, false); + if (missile != NULL) { bb_501kgData_t* kgData = ((bb_501kgData_t*)missile->data); @@ -6167,9 +6197,9 @@ void bb_trigger501kg(bb_entity_t* self) void bb_triggerFaultyWile(bb_entity_t* self) { bb_ensureEntitySpace(&self->gameData->entityManager, 1); - bb_entity_t* explosion = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, BB_EXPLOSION, 1, - self->pos.x >> DECIMAL_BITS, self->pos.y >> DECIMAL_BITS, false, false); - if(explosion != NULL) + bb_entity_t* explosion = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, BB_EXPLOSION, 1, + self->pos.x >> DECIMAL_BITS, self->pos.y >> DECIMAL_BITS, false, false); + if (explosion != NULL) { bb_explosionData_t* eData = (bb_explosionData_t*)explosion->data; eData->radius = 60; @@ -6179,9 +6209,9 @@ void bb_triggerFaultyWile(bb_entity_t* self) void bb_triggerDrillBotWile(bb_entity_t* self) { bb_ensureEntitySpace(&self->gameData->entityManager, 1); - bb_entity_t* drillBot = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, BB_DRILL_BOT, 1, - self->pos.x >> DECIMAL_BITS, -800, false, false); - if(drillBot != NULL) + bb_entity_t* drillBot = bb_createEntity(&self->gameData->entityManager, NO_ANIMATION, true, BB_DRILL_BOT, 1, + self->pos.x >> DECIMAL_BITS, -800, false, false); + if (drillBot != NULL) { bb_drillBotData_t* dbData = (bb_drillBotData_t*)drillBot->data; dbData->targetY = self->pos.y - 256; @@ -6308,10 +6338,8 @@ bb_dialogueData_t* bb_createDialogueData(uint8_t numStrings, const char* firstCh dData->numStrings = numStrings; dData->offsetY = -240; int8_t characterSprite = 0; - if (strcmp(firstCharacter, "Ovo") == 0 || - strcmp(firstCharacter, "???") == 0 || - strcmp(firstCharacter, "Ovo???") == 0 || - strcmp(firstCharacter, "DOCTOR OVO") == 0) + if (strcmp(firstCharacter, "Ovo") == 0 || strcmp(firstCharacter, "???") == 0 + || strcmp(firstCharacter, "Ovo???") == 0 || strcmp(firstCharacter, "DOCTOR OVO") == 0) { characterSprite = bb_randomInt(0, 6); loadWsgInplace("dialogue_next.wsg", &dData->spriteNext, true, bb_decodeSpace, bb_hsd); diff --git a/main/modes/games/bigbug/entity_bigbug.h b/main/modes/games/bigbug/entity_bigbug.h index e9c006ec5..2d4a27d55 100644 --- a/main/modes/games/bigbug/entity_bigbug.h +++ b/main/modes/games/bigbug/entity_bigbug.h @@ -227,13 +227,11 @@ typedef struct bb_entity_t* egg; // tracks the egg to stimulate it. } bb_eggLeavesData_t; - typedef struct { uint16_t stimulation; // once it reaches 600, it turns into a bug. } bb_eggData_t; - typedef struct { bb_entity_t* rocket; // a reference to the booster. To reposition self and Notify to take off at angle = 359. @@ -364,7 +362,6 @@ typedef struct bool secondDialogeDone; } bb_finalBossData_t; - typedef void (*bb_updateFunction_t)(bb_entity_t* self); typedef void (*bb_updateFarFunction_t)(bb_entity_t* self); typedef void (*bb_drawFunction_t)(bb_entityManager_t* entityManager, rectangle_t* camera, bb_entity_t* self); diff --git a/main/modes/games/bigbug/mode_bigbug.c b/main/modes/games/bigbug/mode_bigbug.c index 3b938886c..ff212710e 100644 --- a/main/modes/games/bigbug/mode_bigbug.c +++ b/main/modes/games/bigbug/mode_bigbug.c @@ -188,8 +188,8 @@ static void bb_EnterMode(void) = bb_createEntity(&(bigbug->gameData.entityManager), NO_ANIMATION, true, NO_SPRITE_POI, 1, (foreground->pos.x >> DECIMAL_BITS), (foreground->pos.y >> DECIMAL_BITS) - 234, true, false); - bigbug->gameData.camera.camera.pos.x = (bigbug->gameData.entityManager.viewEntity->pos.x >> DECIMAL_BITS) - 140; - bigbug->gameData.camera.camera.pos.y = (bigbug->gameData.entityManager.viewEntity->pos.y >> DECIMAL_BITS) - 120; + bigbug->gameData.camera.camera.pos.x = (bigbug->gameData.entityManager.viewEntity->pos.x >> DECIMAL_BITS) - 140; + bigbug->gameData.camera.camera.pos.y = (bigbug->gameData.entityManager.viewEntity->pos.y >> DECIMAL_BITS) - 120; bigbug->gameData.camera.camera.width = FIELD_WIDTH; bigbug->gameData.camera.camera.height = FIELD_HEIGHT; @@ -1640,7 +1640,7 @@ static void bb_GameLoop(int64_t elapsedUs) = bb_createEntity(&bigbug->gameData.entityManager, NO_ANIMATION, true, BB_RADAR_PING, 1, bigbug->gameData.entityManager.playerEntity->pos.x >> DECIMAL_BITS, bigbug->gameData.entityManager.playerEntity->pos.y >> DECIMAL_BITS, true, false); - if(radarPing != NULL) + if (radarPing != NULL) { bigbug->gameData.screen = BIGBUG_GAME_PINGING; bb_radarPingData_t* rpData = (bb_radarPingData_t*)radarPing->data; diff --git a/main/modes/games/bigbug/worldGen_bigbug.c b/main/modes/games/bigbug/worldGen_bigbug.c index 6aa01b2f5..0bd47afdd 100644 --- a/main/modes/games/bigbug/worldGen_bigbug.c +++ b/main/modes/games/bigbug/worldGen_bigbug.c @@ -35,14 +35,14 @@ void bb_generateWorld(bb_tilemap_t* tilemap, int8_t* oldBoosterYs) { for (int j = 0; j < TILE_FIELD_HEIGHT; j++) { - tilemap->fgTiles[i][j].pos = i | (j << 7) | (1 << 15); - tilemap->mgTiles[i][j].pos = i | (j << 7); // z is implicitly zero - tilemap->fgTiles[i][j].embed = NOTHING_EMBED; + tilemap->fgTiles[i][j].pos = i | (j << 7) | (1 << 15); + tilemap->mgTiles[i][j].pos = i | (j << 7); // z is implicitly zero + tilemap->fgTiles[i][j].embed = NOTHING_EMBED; tilemap->fgTiles[i][j].entity = NULL; - tilemap->fgTiles[i][j].gCost = 0; - tilemap->mgTiles[i][j].gCost = 0; - tilemap->fgTiles[i][j].hCost = 0; - tilemap->mgTiles[i][j].hCost = 0; + tilemap->fgTiles[i][j].gCost = 0; + tilemap->mgTiles[i][j].gCost = 0; + tilemap->fgTiles[i][j].hCost = 0; + tilemap->mgTiles[i][j].hCost = 0; uint32_t rgbCol = paletteToRGB(levelWsg.px[(j * levelWsg.w) + i]); @@ -135,7 +135,7 @@ void bb_generateWorld(bb_tilemap_t* tilemap, int8_t* oldBoosterYs) case 153: { tilemap->mgTiles[i][j].health = 0; - tilemap->fgTiles[i][j].embed = FINAL_BOSS_EMBED; + tilemap->fgTiles[i][j].embed = FINAL_BOSS_EMBED; break; } default: From 37d35b0b18b1b1445d01d6db9fbc93d410f27576 Mon Sep 17 00:00:00 2001 From: DebrisHauler Date: Sun, 19 Jan 2025 19:00:57 -0500 Subject: [PATCH 16/33] How many commits does it take to screw in a lightbulb? --- assets/bigbug/menu/bb_menu2.png | Bin 8839 -> 8866 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/assets/bigbug/menu/bb_menu2.png b/assets/bigbug/menu/bb_menu2.png index db7aad01ca4e12f5502c85c8edc7ec72fb3833d0..2bb29a67f2a600547ea196d35a77c528a35f850d 100644 GIT binary patch literal 8866 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 literal 8839 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 Date: Sun, 19 Jan 2025 19:22:18 -0500 Subject: [PATCH 17/33] get rid of warnings. crop menu graphic. --- assets/bigbug/menu/bb_menu1.png | Bin 28477 -> 28105 bytes main/modes/games/bigbug/entity_bigbug.c | 12 ++++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/assets/bigbug/menu/bb_menu1.png b/assets/bigbug/menu/bb_menu1.png index 0eddb7cdbdd96ca8c0936aec5c01c2df90c18d1d..890258a06e138ba42c9acb1c96380768e230b068 100644 GIT binary patch literal 28105 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 wo%Ig7m-X1AC&Yr;=86k%P0BkpJ^udcHjc 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 literal 28477 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 diff --git a/main/modes/games/bigbug/entity_bigbug.c b/main/modes/games/bigbug/entity_bigbug.c index eb4870e20..fd3c80b3a 100644 --- a/main/modes/games/bigbug/entity_bigbug.c +++ b/main/modes/games/bigbug/entity_bigbug.c @@ -3077,7 +3077,7 @@ void bb_updateSpaceLaser(bb_entity_t* self) void bb_updateQuickplay(bb_entity_t* self) { - bb_menuData_t* mData; + bb_menuData_t* mData = NULL; for (int eIdx = 0; eIdx < MAX_ENTITIES; eIdx++) { bb_entity_t* menu = &self->gameData->entityManager.entities[eIdx]; @@ -3087,6 +3087,10 @@ void bb_updateQuickplay(bb_entity_t* self) break; } } + if(mData == NULL) + { + return; + } if (self->gameData->btnDownState & PB_LEFT && mData->selectionIdx >= 2) { @@ -4490,7 +4494,7 @@ void bb_drawDeadBug(bb_entityManager_t* entityManager, rectangle_t* camera, bb_e void bb_drawQuickplay(bb_entityManager_t* entityManager, rectangle_t* camera, bb_entity_t* self) { - bb_menuData_t* mData; + bb_menuData_t* mData = NULL; for (int eIdx = 0; eIdx < MAX_ENTITIES; eIdx++) { bb_entity_t* menu = &entityManager->entities[eIdx]; @@ -4500,6 +4504,10 @@ void bb_drawQuickplay(bb_entityManager_t* entityManager, rectangle_t* camera, bb break; } } + if(mData == NULL) + { + return; + } drawRectFilled(0, 0, TFT_WIDTH, TFT_HEIGHT, c000); int16_t xOff = 20; From 9bf10b6b529238c69f410ab013cbb3d0c3518c03 Mon Sep 17 00:00:00 2001 From: DebrisHauler Date: Sun, 19 Jan 2025 20:10:12 -0500 Subject: [PATCH 18/33] spelling fix --- main/modes/games/bigbug/entity_bigbug.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/modes/games/bigbug/entity_bigbug.c b/main/modes/games/bigbug/entity_bigbug.c index fd3c80b3a..7841b0da9 100644 --- a/main/modes/games/bigbug/entity_bigbug.c +++ b/main/modes/games/bigbug/entity_bigbug.c @@ -3255,7 +3255,7 @@ void bb_updateFinalBoss(bb_entity_t* self) bb_setCharacterLine(dData, 8, "Ovo", "But that's a future problem."); bb_setCharacterLine(dData, 9, "Ovo", "It's all very confusing, I know."); bb_setCharacterLine(dData, 10, "Ovo", "Anyways, thanks for playing the game!"); - bb_setCharacterLine(dData, 11, "Ovo", "This uiverse's timeline cannot process your awesomeness!"); + bb_setCharacterLine(dData, 11, "Ovo", "This universe's timeline cannot process your awesomeness!"); dData->curString = -1; dData->endDialogueCB = &bb_afterEnding; From 592de9c91465413a1f81b3bb58621a44fa2da4bb Mon Sep 17 00:00:00 2001 From: gelakinetic Date: Mon, 20 Jan 2025 01:15:25 +0000 Subject: [PATCH 19/33] Fix cppcheck warning (bounds check) Fix doxygen error (stray backtick) Formatting --- docs/EMULATOR.md | 2 +- main/modes/games/bigbug/entityManager_bigbug.c | 14 ++++++++++---- main/modes/games/bigbug/entity_bigbug.c | 4 ++-- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/docs/EMULATOR.md b/docs/EMULATOR.md index f7b25c400..afcd66113 100644 --- a/docs/EMULATOR.md +++ b/docs/EMULATOR.md @@ -249,7 +249,7 @@ are `azerty`, `dvorak`, or `colemak`. ## Console Commands The emulator supports a small number of commands in the console, which can be opened by pressing `F4` or -the `\`` / `~` key. These console commands can also be used from a [replay script](#recording-and-playing-inputs). +the `\` / `~` key. These console commands can also be used from a [replay script](#recording-and-playing-inputs). To get a complete list of available commands, type `help` into the console. For the usage of a specific command, type `help ` into the console. diff --git a/main/modes/games/bigbug/entityManager_bigbug.c b/main/modes/games/bigbug/entityManager_bigbug.c index d2418fa7e..2431687e4 100644 --- a/main/modes/games/bigbug/entityManager_bigbug.c +++ b/main/modes/games/bigbug/entityManager_bigbug.c @@ -570,9 +570,12 @@ void bb_drawEntity(bb_entity_t* currentEntity, bb_entityManager_t* entityManager } else if (entityManager->sprites[currentEntity->spriteIndex].brightnessLevels == 6) { - // Don't draw unallocated sprites - if (NUM_SPRITES > currentEntity->spriteIndex && !entityManager->sprites[currentEntity->spriteIndex].allocated) + // If the sprite is out-of-bounds + if (currentEntity->spriteIndex >= NUM_SPRITES + // Or the sprite is in-bounds, but not allocated + || !entityManager->sprites[currentEntity->spriteIndex].allocated) { + // Immediately return return; } @@ -616,9 +619,12 @@ void bb_drawEntity(bb_entity_t* currentEntity, bb_entityManager_t* entityManager } else { - // Don't draw unallocated sprites - if (NUM_SPRITES > currentEntity->spriteIndex && !entityManager->sprites[currentEntity->spriteIndex].allocated) + // If the sprite is out-of-bounds + if (currentEntity->spriteIndex >= NUM_SPRITES + // Or the sprite is in-bounds, but not allocated + || !entityManager->sprites[currentEntity->spriteIndex].allocated) { + // Immediately return return; } diff --git a/main/modes/games/bigbug/entity_bigbug.c b/main/modes/games/bigbug/entity_bigbug.c index 7841b0da9..bb2039734 100644 --- a/main/modes/games/bigbug/entity_bigbug.c +++ b/main/modes/games/bigbug/entity_bigbug.c @@ -3087,7 +3087,7 @@ void bb_updateQuickplay(bb_entity_t* self) break; } } - if(mData == NULL) + if (mData == NULL) { return; } @@ -4504,7 +4504,7 @@ void bb_drawQuickplay(bb_entityManager_t* entityManager, rectangle_t* camera, bb break; } } - if(mData == NULL) + if (mData == NULL) { return; } From bdeefa5adf36c0c824d1ed60b371427ff4c3ddb9 Mon Sep 17 00:00:00 2001 From: gelakinetic Date: Mon, 20 Jan 2025 01:57:08 +0000 Subject: [PATCH 20/33] Fix bounds check for real --- .../modes/games/bigbug/entityManager_bigbug.c | 29 +++++++------------ 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/main/modes/games/bigbug/entityManager_bigbug.c b/main/modes/games/bigbug/entityManager_bigbug.c index 2431687e4..a467323e6 100644 --- a/main/modes/games/bigbug/entityManager_bigbug.c +++ b/main/modes/games/bigbug/entityManager_bigbug.c @@ -568,17 +568,16 @@ void bb_drawEntity(bb_entity_t* currentEntity, bb_entityManager_t* entityManager { currentEntity->drawFunction(entityManager, camera, currentEntity); } + // If the sprite is out-of-bounds + else if (currentEntity->spriteIndex >= NUM_SPRITES + // Or the sprite is in-bounds, but not allocated + || !entityManager->sprites[currentEntity->spriteIndex].allocated) + { + // Immediately return + return; + } else if (entityManager->sprites[currentEntity->spriteIndex].brightnessLevels == 6) { - // If the sprite is out-of-bounds - if (currentEntity->spriteIndex >= NUM_SPRITES - // Or the sprite is in-bounds, but not allocated - || !entityManager->sprites[currentEntity->spriteIndex].allocated) - { - // Immediately return - return; - } - uint8_t brightness = 5; int16_t xOff = (currentEntity->pos.x >> DECIMAL_BITS) - entityManager->sprites[currentEntity->spriteIndex].originX - camera->pos.x; @@ -619,15 +618,6 @@ void bb_drawEntity(bb_entity_t* currentEntity, bb_entityManager_t* entityManager } else { - // If the sprite is out-of-bounds - if (currentEntity->spriteIndex >= NUM_SPRITES - // Or the sprite is in-bounds, but not allocated - || !entityManager->sprites[currentEntity->spriteIndex].allocated) - { - // Immediately return - return; - } - drawWsgSimple(&entityManager->sprites[currentEntity->spriteIndex].frames[currentEntity->currentAnimationFrame], (currentEntity->pos.x >> DECIMAL_BITS) - entityManager->sprites[currentEntity->spriteIndex].originX - camera->pos.x, @@ -642,7 +632,8 @@ void bb_drawEntity(bb_entity_t* currentEntity, bb_entityManager_t* entityManager currentEntity->currentAnimationFrame = currentEntity->animationTimer / currentEntity->gameFramesPerAnimationFrame; // if frame reached the end of the animation - if (currentEntity->currentAnimationFrame >= entityManager->sprites[currentEntity->spriteIndex].numFrames) + if (currentEntity->spriteIndex < NUM_SPRITES + && currentEntity->currentAnimationFrame >= entityManager->sprites[currentEntity->spriteIndex].numFrames) { switch (currentEntity->type) { From 39bbed1e351468ab157c4334c280f2798f181108 Mon Sep 17 00:00:00 2001 From: gelakinetic Date: Tue, 14 Jan 2025 10:01:33 +0000 Subject: [PATCH 21/33] Update pyFlashGui to reboot after flashing Add internal check for esptool version Update README, requirements.txt Ignore bin files Update stray IDF versions --- attic/components/hdw-bzr/include/hdw-bzr.h | 2 +- .../hdw-spiffs/include/hdw-spiffs.h | 2 +- tools/pyFlashGui/.gitignore | 1 + tools/pyFlashGui/README.md | 12 +++++--- tools/pyFlashGui/pyFlashGui.py | 29 +++++++++++++++++++ tools/pyFlashGui/requirements.txt | 3 +- 6 files changed, 42 insertions(+), 7 deletions(-) create mode 100644 tools/pyFlashGui/.gitignore diff --git a/attic/components/hdw-bzr/include/hdw-bzr.h b/attic/components/hdw-bzr/include/hdw-bzr.h index a2cb97736..62f2dc729 100644 --- a/attic/components/hdw-bzr/include/hdw-bzr.h +++ b/attic/components/hdw-bzr/include/hdw-bzr.h @@ -3,7 +3,7 @@ * \section bzr_design Design Philosophy * * The buzzers are driven by the LEDC + * href="https://docs.espressif.com/projects/esp-idf/en/v5.2.3/esp32s2/api-reference/peripherals/ledc.html">LEDC * peripheral. This is usually used to generate a PWM signal to control the intensity of an LED, but here it * generates frequencies for the buzzers. * diff --git a/attic/components/hdw-spiffs/include/hdw-spiffs.h b/attic/components/hdw-spiffs/include/hdw-spiffs.h index d3d3a04ea..15a962ab8 100644 --- a/attic/components/hdw-spiffs/include/hdw-spiffs.h +++ b/attic/components/hdw-spiffs/include/hdw-spiffs.h @@ -4,7 +4,7 @@ * * SPIFFS is a file system intended for SPI NOR flash devices on embedded targets. It supports wear levelling, file * system consistency checks, and more. The full API reference can be found here: SPIFFS + * href="https://docs.espressif.com/projects/esp-idf/en/v5.2.3/esp32s2/api-reference/storage/spiffs.html">SPIFFS * Filesystem. * * Ths Swadge treats SPIFFS as a read-only file system. diff --git a/tools/pyFlashGui/.gitignore b/tools/pyFlashGui/.gitignore new file mode 100644 index 000000000..f3ac583d0 --- /dev/null +++ b/tools/pyFlashGui/.gitignore @@ -0,0 +1 @@ +*.bin \ No newline at end of file diff --git a/tools/pyFlashGui/README.md b/tools/pyFlashGui/README.md index 57a5f7a95..e7b372cca 100644 --- a/tools/pyFlashGui/README.md +++ b/tools/pyFlashGui/README.md @@ -8,11 +8,14 @@ This Python script polls for USB serial ports which match the VID & PID of an ES The UI will be green when a Swadge is successfully flashed. It will be red if the flash failed. +> [!NOTE] +> As of January 2025 the program may erroneously flash red, falsely indicating a failure, when rebooting after a successful flash. If the Swadge reboots to a valid screen, the flash was successful. + # Driver Dependencies ## Windows -Flashing an ESP32-S2 over USB on Windows requires a specific driver to be installed. This driver only needs to be installed once. Instructions can be found in the [ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/v4.4.3/esp32s2/api-guides/dfu.html#usb-drivers-windows-only) and are copied below: +Flashing an ESP32-S2 over USB on Windows requires a specific driver to be installed. This driver only needs to be installed once. Instructions can be found in the [ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/v5.2.3/esp32s2/api-guides/dfu.html#usb-drivers-windows-only) and are copied below: > `dfu-util` uses _libusb_ to access the device. You have to register on Windows the device with the WinUSB driver. Please see the [libusb wiki](https://github.com/libusb/libusb/wiki/Windows#How_to_use_libusb_on_Windows) for more details. > @@ -23,7 +26,7 @@ Flashing an ESP32-S2 over USB on Windows requires a specific driver to be instal ## Linux -The ESP-IDF Programming Guide also [specifies a udev rule for Linux](https://docs.espressif.com/projects/esp-idf/en/v4.4.3/esp32s2/api-guides/dfu.html#udev-rule-linux-only): +The ESP-IDF Programming Guide also [specifies a udev rule for Linux](https://docs.espressif.com/projects/esp-idf/en/v5.2.3/esp32s2/api-guides/dfu.html#udev-rule-linux-only): > udev is a device manager for the Linux kernel. It allows us to run `dfu-util` (and `idf.py dfu-flash`) without `sudo` for gaining access to the chip. > @@ -47,6 +50,7 @@ The following Python modules must be installed: | ``tkinter`` | https://tkdocs.com/tutorial/install.html | Tkinter (and, since Python 3.1, ttk, the interface to the newer themed widgets) is included in the Python standard library. | | ``pyserial`` | https://pyserial.readthedocs.io/en/latest/pyserial.html | ```pip install pyserial```| | ``esptool`` | https://docs.espressif.com/projects/esptool/en/latest/esp32s2/installation.html | ```pip install esptool``` | +| ``semver`` | https://python-semver.readthedocs.io/en/latest/index.html | ```pip install semver``` | For the script to actually flash firmware, the following files must be in the same directory as ``pyFlashGui.py``: @@ -68,12 +72,12 @@ cp ./build/bootloader/bootloader.bin ./build/swadge2024.bin ./build/partition_ta 1. Download and install the dependencies * [Python](https://www.python.org/downloads/) * ```bash - python -m pip install -r requirements.txt + python3 -m pip install -r requirements.txt ``` * [Tkinter Installation](https://tkdocs.com/tutorial/install.html) (may not be necessary) 1. Download the final Swadge firmware from the Releases tab: https://github.com/AEFeinstein/Super-2024-Swadge-FW/releases/ 1. Run the programmer script (``python3 pyFlashGui.py`` from a terminal) -1. Switch the Swadge's power to USB (right position) +1. Switch the Swadge's power to USB 1. Hold the PGM button down (Up on the directional pad) * Note, for the first Hot Dog Prototype revision, the PGM button is Left, not Up 1. Plug the Swadge into the computer with a USB-C cable diff --git a/tools/pyFlashGui/pyFlashGui.py b/tools/pyFlashGui/pyFlashGui.py index eb5c2113c..6ddd24a85 100644 --- a/tools/pyFlashGui/pyFlashGui.py +++ b/tools/pyFlashGui/pyFlashGui.py @@ -1,7 +1,12 @@ +#!/usr/bin/python + import serial.tools.list_ports import serial.tools.list_ports_common import threading import esptool +import time +import semver + try: # for Python2 import Tkinter as tk @@ -35,6 +40,10 @@ def run(self): # Try to flash the firmware try: + # Wait two seconds + time.sleep(2) + + # Flash it esptool.main([ "--chip", "esp32s2", "-p", str(self.serialPort.device), @@ -53,16 +62,36 @@ def run(self): self.labelText = "Flash succeeded on " + str(self.serialPort.device) self.labelColor = "green" updateUI = True + except Exception as e: # It failed. Display a bad red message self.labelText = "Flash failed on " + str(self.serialPort.device) + " because: " + str(e) self.labelColor = "red" updateUI = True + # Wait two seconds + time.sleep(2) + + try: + # Reboot out of the bootloader + esptool.main([ + "-p", str(self.serialPort.device), + "--after", "hard_reset", + "chip_id" + ]) + except: + pass class ProgrammerApplication: def init(self): + minEspToolVer = '4.9.0-dev.3' + esptoolVer = semver.Version.parse(esptool.__version__.replace('.dev', '.0-dev.')) + if semver.compare(str(esptoolVer), minEspToolVer) < 0: + print('Please update esptool to at least 4.9.dev3 and run again. Try:') + print(' python -m pip install -r requirements.txt') + exit() + global updateUI # Keep track of all the threads diff --git a/tools/pyFlashGui/requirements.txt b/tools/pyFlashGui/requirements.txt index 16d1facde..4077f740d 100644 --- a/tools/pyFlashGui/requirements.txt +++ b/tools/pyFlashGui/requirements.txt @@ -1,2 +1,3 @@ -esptool==4.8.1 +esptool==4.9.dev3 pyserial==3.5 +semver==3.0.2 From 0afa8fcfaada61db084d9a0d73a62b4a9cecacb1 Mon Sep 17 00:00:00 2001 From: JVeg199X <97848253+JVeg199X@users.noreply.github.com> Date: Tue, 14 Jan 2025 03:14:57 -0500 Subject: [PATCH 22/33] tiny adjustments to Pango and Gamepad (#394) Loop Pango music Turn off gamepad menu LEDs --- main/modes/games/pango/paSoundManager.c | 7 ++++--- main/modes/utilities/gamepad/gamepad.c | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/main/modes/games/pango/paSoundManager.c b/main/modes/games/pango/paSoundManager.c index 68936aa1c..96cb2316c 100644 --- a/main/modes/games/pango/paSoundManager.c +++ b/main/modes/games/pango/paSoundManager.c @@ -57,6 +57,10 @@ void pa_freeSoundManager(paSoundManager_t* self) */ bool pa_setBgm(paSoundManager_t* self, uint16_t newBgmIndex) { + // All BGM's are intended to loop! + midiPlayer_t* player = globalMidiPlayerGet(MIDI_BGM); + player->loop = true; + if (self->currentBgmIndex == newBgmIndex) { return false; @@ -71,9 +75,6 @@ bool pa_setBgm(paSoundManager_t* self, uint16_t newBgmIndex) if (newBgmIndex != PA_BGM_NULL) { loadMidiFile(PANGO_BGMS[newBgmIndex - 1], &self->currentBgm, true); - - midiPlayer_t* player = globalMidiPlayerGet(MIDI_BGM); - player->loop = true; } self->currentBgmIndex = newBgmIndex; diff --git a/main/modes/utilities/gamepad/gamepad.c b/main/modes/utilities/gamepad/gamepad.c index 6700eb5ef..e37e9717d 100644 --- a/main/modes/utilities/gamepad/gamepad.c +++ b/main/modes/utilities/gamepad/gamepad.c @@ -295,6 +295,7 @@ void gamepadEnterMode(void) // Initialize menu renderer gamepad->renderer = initMenuManiaRenderer(NULL, NULL, NULL); + gamepad->renderer->ledsOn = false; // Set up the IMU accelSetRegistersAndReset(); From 3d177404a3cfca6a1f5a1a27b9180a1420368980 Mon Sep 17 00:00:00 2001 From: gelakinetic Date: Tue, 14 Jan 2025 10:16:17 +0000 Subject: [PATCH 23/33] Add headless mode to pyFlashGui --- tools/pyFlashGui/README.md | 12 ++++++ tools/pyFlashGui/pyFlashGui.py | 70 +++++++++++++++++++++++----------- 2 files changed, 59 insertions(+), 23 deletions(-) diff --git a/tools/pyFlashGui/README.md b/tools/pyFlashGui/README.md index e7b372cca..aaf0900b0 100644 --- a/tools/pyFlashGui/README.md +++ b/tools/pyFlashGui/README.md @@ -11,6 +11,18 @@ The UI will be green when a Swadge is successfully flashed. It will be red if th > [!NOTE] > As of January 2025 the program may erroneously flash red, falsely indicating a failure, when rebooting after a successful flash. If the Swadge reboots to a valid screen, the flash was successful. +This script may also be run in headless mode when a GUI is not required or desired. + +``` +usage: pyFlashGui.py [-h] [--headless] + +Optional app description + +options: + -h, --help show this help message and exit + --headless Run in headless mode without a GUI +``` + # Driver Dependencies ## Windows diff --git a/tools/pyFlashGui/pyFlashGui.py b/tools/pyFlashGui/pyFlashGui.py index 6ddd24a85..363f99240 100644 --- a/tools/pyFlashGui/pyFlashGui.py +++ b/tools/pyFlashGui/pyFlashGui.py @@ -6,6 +6,8 @@ import esptool import time import semver +import argparse +import os try: # for Python2 @@ -17,13 +19,16 @@ global updateUI updateUI: bool = True +global headless +headless: bool = True + class LabelThread(threading.Thread): def __init__(self, _serialPort: serial.tools.list_ports_common.ListPortInfo): super(LabelThread, self).__init__() self.serialPort = _serialPort - self.labelText = str(self.serialPort.device) - self.labelColor = "gray" + self.labelText: str = str(self.serialPort.device) + self.labelColor: str = "gray" def getLabelText(self) -> str: return self.labelText @@ -33,6 +38,7 @@ def getLabelColor(self) -> str: def run(self): global updateUI + global headless # Draw the label self.labelText = "Attempting flash on " + str(self.serialPort.device) @@ -84,6 +90,10 @@ def run(self): class ProgrammerApplication: + def __init__(self, isHeadless: bool): + global headless + headless = isHeadless + def init(self): minEspToolVer = '4.9.0-dev.3' esptoolVer = semver.Version.parse(esptool.__version__.replace('.dev', '.0-dev.')) @@ -93,6 +103,7 @@ def init(self): exit() global updateUI + global headless # Keep track of all the threads self.threads: list[LabelThread] = [] @@ -105,23 +116,26 @@ def init(self): self.numCols: int = 1 # Create the UI root - self.root = tk.Tk() + if not headless: + self.root = tk.Tk() - # Set up a menu, just to quit - menubar = tk.Menu(self.root) - filemenu = tk.Menu(menubar, tearoff=0) - filemenu.add_command(label="Exit", command=exit_function) - menubar.add_cascade(label="File", menu=filemenu) - self.root.config(menu=menubar) + # Set up a menu, just to quit + menubar = tk.Menu(self.root) + filemenu = tk.Menu(menubar, tearoff=0) + filemenu.add_command(label="Exit", command=exit_function) + menubar.add_cascade(label="File", menu=filemenu) + self.root.config(menu=menubar) - # Override the close button - self.root.protocol("WM_DELETE_WINDOW", exit_function) + # Override the close button + self.root.protocol("WM_DELETE_WINDOW", exit_function) - # Start the main UI loop - self.root.winfo_toplevel().title("ESP32-S2 Swadge Programmer") + # Start the main UI loop + self.root.winfo_toplevel().title("ESP32-S2 Swadge Programmer") - # Set the initial window size - self.root.geometry("500x200") + # Set the initial window size + self.root.geometry("500x200") + else: + print(os.path.basename(__file__) + 'Running in headless mode') # Run this loop until it should quit self.shouldQuit: bool = False @@ -157,26 +171,31 @@ def init(self): self.threads.remove(thread) updateUI = True - # Update UI - self.adjustRows() - self.root.update_idletasks() - self.root.update() + if not headless: + # Update UI + self.adjustRows() + self.root.update_idletasks() + self.root.update() def exit(self): global updateUI + global headless self.shouldQuit = True if len(self.threads) > 0: - self.root.update() + if not headless: + self.root.update() # Wait for all threads to actually stop for thread in self.threads: thread.join() self.threads.remove(thread) updateUI = True - # Destroy the UI - self.root.destroy() + if not headless: + # Destroy the UI + self.root.destroy() def adjustRows(self): global updateUI + global headless if updateUI: updateUI = False @@ -222,5 +241,10 @@ def exit_function(): if __name__ == "__main__": - app = ProgrammerApplication() + # Instantiate the parser + parser = argparse.ArgumentParser(description='Optional app description') + parser.add_argument('--headless', action='store_true', help='Run in headless mode without a GUI') + args = parser.parse_args() + + app = ProgrammerApplication(args.headless) app.init() From ad42d6c4baf9eec9aafb0a734b15f3f6d2b6148f Mon Sep 17 00:00:00 2001 From: Dylan Whichard Date: Wed, 15 Jan 2025 00:10:52 -0800 Subject: [PATCH 24/33] MAAAGFEEEEEST (#395) --- assets/fonts/retro_logo.font.png | Bin 6624 -> 6650 bytes assets/intro/magfest.bin | 1 + main/modes/system/intro/introMode.c | 182 ++++++++++++++++++-------- main/modes/system/mainMenu/mainMenu.c | 3 - main/modes/test/keebTest/keebTest.c | 5 +- main/modes/test/keebTest/keebTest.h | 2 +- 6 files changed, 136 insertions(+), 57 deletions(-) create mode 100644 assets/intro/magfest.bin diff --git a/assets/fonts/retro_logo.font.png b/assets/fonts/retro_logo.font.png index 1cb941057222163fdf2800fa0ce0758bf37d8c97..5a51773d9915aade09641c542a47dcad8162fb51 100644 GIT binary patch delta 6646 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 pbAe&i9Lc*XIJG literal 6624 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 L&Gl+-K6v_nvGg|T diff --git a/assets/intro/magfest.bin b/assets/intro/magfest.bin new file mode 100644 index 000000000..a71b459ee --- /dev/null +++ b/assets/intro/magfest.bin @@ -0,0 +1 @@ +€€€€€€€€€€€€€€€€€€€€€€€€~€€€€€€€€€€€€€€€€€€€€€€€€€€€~~~~~~~~~~~~€€€€€€€€€€€~~}}}}}}|||||||}}|}}}}~~€‚ƒ„„…………†††………„ƒƒ~}{{{zzyzyyyyzyyyyzyz{|}€ƒƒ‚ƒ€yrojddeglsvy|€ƒ†‰‹ŒŽ’“•™ž¡£¥§§©ª©¨©©¨¥¢Ÿœ™•“‰‡ƒ|zwtqnlifdb`]\[YXWXXXXXXXZZ[^`afknqx|†‹Ž’––•–”’‘Ž‡|vqmha\WVUUX[]`degjmosy}‰–Ÿ§ª°¸¹º½¾½»º´®ª¦¢š•’ŽŠˆ…ƒ‚|{zxwuw{}|~€‚‡ˆ…†ˆ„ƒ„~~}xvwustwvvy{{{}~~}{xvrpqrqnmmklpnjironrotzslpowqq€‡Š‹„„ƒ‹—–‘“‘‡Š˜ž˜“–¤±¸©ž‘|oeXgxsehf[`wŠ˜ˆvom„yioucwpgk|~py€‹›«© –š™vz|‡€vofh]isokTQ88CSkifr„‹ˆŒ…–¦ª©¡”˜©¢¤™¢¦ˆ‡Ž’‡•ˆ~qhVr}iŽŒ‘©pr•’w†‰ŽŽ·‹xdysfŽ…€š‡ˆ—x{vdECG,01MvrŽbf›n|vgm›¾˜†n†ž—œ‰“Ÿ•†€fL<,0$/JGW\s› ¬¤Š«È¡ˆŠ­ª›£™•¸Èž£ºÈ¼œ‹¯Å fr“XEAUacI:MTicKI€Ä®kk§²“lWgŸªyh†»È´“|~zbA?ZvqG+Asm?3>TmPIo´¿¤‚º¢‘RQ±§›«¿ÚáÁ‰ŽˆfTr…žª€t„ƒZRZaqtcbŠ¯Å•j‹ˆtvT@u–•­«°±²œ…Œ†bWp™¥{FJSFOR\w†”„š„TKkqLPG@cŸ§¡ž˜ˆ‰yfo ±£¨«µ°ˆdiwv}s_n•znŽ‰gƒ~tX_ILvjn™³´³¤™¨¹©˜•„˜‰bMDXj|sbrmWU`†–°³½¶¸‚Ze`Š•‡†™¤­©‘§£“|Š•„f`\\v‚UQNA\uacvtuj`]O$&Nz—›”¥¹®†oi{‹†Ž…€…wBJN\„v”¦›’‰¸¹«°¿¼±ŒZjkh™µ·ÑÔÌ´xs_ar™ÓÆ‘tb:5EU{{qˆ¦˜¥ hG\alc4AUK[xvhVLGWrkk““pmfUUOWv–¢ŽŠ—…}–™€‹œ™ŒeGNZX»ÓÏ»º¸’qs„¤®¨¯¹¤t\UQ\`žŒŠš™Š|quˆ‡˜¸‘vvyˆsˆž¬µ¬ŸŽ—ˆjkvnuvXaiYƒˆz]7:N_y¨œ‡–|RA9Ud[‹¡¹Ã¤Ÿš|hhdjw†zT:5(2_už“x‹‡nuuh„›—ˆzpcqii‘¤µ¶¼¹Ÿ‹pbfzž´¨µ»¡†otcv{}ž£¨³µ£†labb}~ƒ£­¥ˆkYSfy}’Ž­­ePQKOOvŠ—ˆ†„}l^e…|`YE@;/Ag…”ª•…~dcXZ{¸¯˜›“ˆf_Š™¢ª¢s[\teeq¥“pr‹‡x‘ˆš’š®­’ŠvŠ’{€““}lfct‰“¨¯¤}fhYcR`‡™¶Á²œŠz{nXe“ˆykZY86Yhƒž¹¢‘…‹e_‘¥…w~†scn€Œq{¡|qgNBRRaUR{yi|lqid†˜’¦±§­¶—|qj¬¬²ºltYFLGk£œ|‚{jO6;mvoˆœ°¨ž›š‡v–¦˜–Št_cw‡Š‘‰©¢…ˆ†ddƒ• œ’y^_rpfnw‰šž¦‘lcgUaih}§¿¹«¡‹n\^^l{{‹ ©•u^WS@hŸ”Œš”„uaXb]Y‘±“~‹†rQ_ˆ–ª‘¡Œvyzs‚—”Ÿ¢‰ƒb4Rpjfn‡¥Ë™x›š€hm¥™…‹œ•€ƒys~„ƒŽƒcc`VRYf^Lq¶¡~Š||gw›˜‹ ¸qcWcbµ ”Œ‘a[SG[]Ž«w‡ŽjETmv„–·©ˆ„‡€ŠŠˆ“Šzkaf…”ª ”ª¦z}‡•zq‘¦™†|vyhcl‚‹“© ŒxmRBFKinq©Î©šž‹ƒt\p„x€¡¯ ‰sfiUNs™•‚|…wSTSdx„‡†q`cgaq‡Œ©¢‹‹”“‚or„•‹™ ˜–|[Y^b„‡¢§£Žzl„¡vcv‘•yyš­™‚tum[l†ŒˆŠ†qcX`†«³£™u†oexŒŒ‰’Ž~_UXQa‚¦¾¬ƒYELURq‘Ÿ«ŒgddvoZt””ˆ†®¦… ‘~x…‹ww†||‡…‚€tŠ’o\`Š „y—Ÿ’•ŽœŒonig†Œš‘ycK4AYj­º¹°—t[NYfs~“«ª™oeZ\Xh‡œ§Œoolerw~—–…tnwdku|›¡•tk‰¨’‰‘œ›Ž’“‘‚lduonq„–‰x†eg`bu}…©±©®¡ŽsV^€‘}‚›–zYPWTr‡•šŸ¥Œ]Ynmlw„˜’qkpqf_n˜š¨šp\lqpu~—”…vs~ŽwRe€‚jbt“–¤¢tpoolp•€‹˜˜~fe}¢›²•{‚|{z‹š£š™“kUjy‚„•œŒYAIXLe™¤§¢œyMMVZev«¥†uTU{”––žzgi…qpsxrchˆ€vw{gWbŠ“œª¬¥‡mu„ŒŸqor‚o`l•‹Œ›’ijm`^o–´»¸°©—fO\fq‡¥¯§~NP\`m‡¥¯°¢‘wpgbxzyyunqep„icj}‹ˆz|…kZoztŒ…Šw†€frlq‚Š–¼Ñ±”oLS_aƒ¤© e]QJZo‚”šž¡‚w‡h_‡’™±»¹º¤~\[yšœ‰‡{c@4Rs‰—œ£—{qnagbb€…zoŠŠ—™„rln|Œ¥¤——|obewyx•¥–bEEQ]mŠ °¦ŒxfZXs„ƒ˜™‹q_`vqk—³¹¦—†…‚[fŽ¦²±´±—}upcZd{’„‡vVIJUl|–œ©ƒovinzom~Šˆž­²rlopwv‡……{k]Zm}‡Œ‰€t]RMbxbp—°¤–Šu|ul‡©´°—}bUbcoŽ¥·¸¥Œ‡w^dm¥ˆyspzzkbof`ngt—™…ž¦“}qupuniz†‹‰‡Š†}hU^x€€•œ’†iPTciž©° sPK\{{w¥­—Šˆ†|}Š„Š’„gc_UZt—§¦–Š†vikx‘¤Ÿž§†x†‡‚€ƒ€}txpkcHM_kzˆ“¤¡¤š…vTTYWhw“ šš›”wa\k†Œ˜œŽ„sabr––Ž•‚jMV}zl‚•‘dc~qŒ¡¨°§”w[NNWh…¥²©£“~xuse¡Ž¥ ˜’ˆ{z~o_Vmxzw|viq{‚„ŒŠ™ “ˆqVeoXg‹ƒ„Ž‘—lSiy†£žYHQ\g„›¥«¬£^MNlxr‰”“‡wzv{€|‘˜˜ŽmaZOWpˆž¡“““ƒw‹‰…­·¢¡›Š{€’‡zrt‡|‚„n`TQ[hmŸ¨™ƒgJRgYx ž£¥¤’|cadb™¬£}_faSs‰ž ™ŠoVOIZˆ†‡wme`{ƒ† «µ«‰xqXBWn‡¡›™²§‰‰~||‰œ›œ’ˆˆƒ‚}fZX^r“Ž„Œ|odUj…„¤´®t\`i_ƒˆ›•Œ~mre]k‡¡ªtXWIDetŽ¬¯±§t^`IJeŠ¨™z‹Ÿ‚o|‹“††¡›…umpe`s~ˆ‹“–ƒœ™~‡Œ}‘ƒ~qqil‹‰†tYM_mvŠ–«·£|^ZHBjŸœ¢´¤{^kw`^z¬µ’rq|j]avŠ•—žž}hfcdnŠƒckzˆv”¤˜v…€l`jlgmli–š¯ž›…so­–‰–­™~lqs_[„¢š“Žf>Jj^cˆ¤¼±„elXGax•›‡”š†lfxvqv‰£˜wmuztfx‹žœŒŠeacdpzypx|”‚p‡’pm‘ƒ~‡€nciej„«º¦¨¯£l[ˆž‘‹¥ŸvXkpbcvŽ›Ž~wX;Kem{˜«¶¥‚ndS`sƒ£­¢™ˆuwilnjz‚ŒŽ‡wiYoplšš‹‰sT`ljjg|—“’‘š‡fkƒ…{~ŒmQ[haw­±ªœš¢‹ig‚±ª˜‰pa`^nƒ¨¤šxZFMWcb‚­ª›Œ~upcVw˜‘Ž†‹zq|ƒ}flntuzz‹¡„}Š‹t\ƒžž‹ƒ–’tSVef\uŽŠƒ’klw‘ŠlMQVPu£ºË½²ª‹hduŠ¢®«²gf\Zhn|–™—whN?c|ns“ž‡pu‹t\š¦šƒ‹œ†stˆƒmil~~‡—qjvfGmŽ‘œœ’lCARUf‹¦®¦ž†vfp}†–—ŒvLPjae³¹ª›”“w[`”±­¢¥Že]Ngy’¦«Ÿ‰ujaNSt{z‡™Œy‹ycez‡Šˆˆ“’†€{aZfovŠ“œ«ŽzkQ\‡“’”š˜sD8Qm€ ®¬¦Œ€sljz‘—£›€kbUdsz—§®¢Ž‚‚“£Žz{wc€Œu†‹ˆ}qqˆhWf}{njt†š¡ŽŠ‘€nhlt‰•ž”¤™U8CPi‚‹¡¸­nbcgiw”¢©lXPHThl‹Ÿ¥«™vjz‰“‚vuhLVkr¦¶¶¢†€€|xz„˜ž„u}€|ƒ“w…}wl}††‚dnznjjs|¤©Š’’‡v^Yp{‘¥¢ª™X;E\‚ˆ¨«NUfjp‹©·ª}VQD<[ew–¦³©ƒ€Œxmu‚˜•|nuoZc`x—§®ªž‹Š‰…†‹‡‹{lz}†‡›šŒthpv|{ditokov‡²{Š|bZc}†™¯£•ˆYBE_†“­°®ƒSQ[br¤»µ…c]DIaPi•¦¬Š™~n|ƒ“‚mkqh^gcy–£­¨œ“‚{…‚}Žˆqu’ˆpœ¡„ƒxsxyz‚zd[Wcil¯²šŽ~dQUfŽœ­¯•}dIIq€|–©¢›…XS]ax–«§–~aRHX]^~—Ÿ£›‹~€…‡„Œxjneo‚h^zŠ’˜™¢› |omxmn™“Ž£”Šmert}ŒSDV_x—“¡¯˜ufXSZdz µ±˜„s]Lf|z•«²§™wtcBOh–šŠƒxdkgWafz‰“•–‹„„§—m_dg`xƒ‘……ŠŒŠŒ”›¡–•‚h\mŠŸŽ‹š˜€s~|tz†••ymU<:K…˜¬Â¿eW__^t“±±–…]S|uj›”}s††]Ym~„x|ooy{|~y~Š‰ˆ §…^ffon€“‚pku‰¨¶¡”ˆqcUN„£’£­©•tt~~t{Ž˜–qfbK7Sq}´±†fid^ey•Ž‘ˆeZl|†‘™£¦’‰}‚’wblvtgl€ƒˆ‚‚‚saWdbmŒ–¢¢””†XFa€}•«§hwŠ~«´¢•{kX`Ž’†–©•ufr}‡~‡ œƒmf]B=[i™¦·½’w‚v]eu„ˆƒ…“}pzw}‹€‚€zu}‘‡qkpgQXk‚ˆt•toolr†•–—’Ž‹}t^qˆv~‹˜xnt{‚¦µ§–Šuibt†‚ž§–}z…†ŠrkjaThi_t‰’™Ÿ‹|Š€mx}„{qxzt{{‚Ž“›‰…Œ}‡€upb`c`r›‹t‰‹xompw{”™ˆ…|otz‡yw‹†wsqv€™§ ˆˆoy‰~|ˆ–‚‚ˆ†††umgdpmZf‚‡†š ‘‹“ˆ€‚~}xmgq~zs…“‘–Žƒ…„qmi\agmŒx|‘sˆ‡wz{ztptq–‰„•–…‰‚wkt€Š’™˜‰†xyŠ{w…Š€”•ˆ‡‘‹Ž„€„xyoniYWnsv„‰……Œˆ‹‹Œ†ƒsvwtu‚|„ˆ€…€…vlpvt€…}zskjmrwŒ‰ƒ‡‡ˆ‡€ƒˆƒ‰‡†‰‡uoor{„””Œ‰‰Š‹‹Œ‡…}ƒ‰Ž…|tmklvˆ‰€xsqoqx~‚„„€€„‚}urrvz†‹‰……†„„‹‹‹ˆ~urtuwtqnfaair{‚€€‚€‚…‡„…‡‡†††‡ˆ‡†‚‚‡Œ’““’‹„€~~‚ƒ}yy}‚ˆ†ˆ†€}yy~„‹Ž‹ƒwssv{‚ƒƒ€€}xsnihlv{{zyyxxˆ‹‹‰†€|xx{{wsonqtwy|||{z~‚ƒ€~ƒƒ…‡ˆ‰‰‰†„†ŒŽ–—”Œˆ‡„ƒ~yz~‚|zxwx}€„†…„‚|urtw|~‚}|zyuqsy…‡Šˆƒ„ƒ‚…‡‡‰‹Š†‚{rnosvtqppomptvy{€…ˆ‰‡…ƒ€‚€‚„‡‡„„‰——’Œˆ„€‚‚€}||€€€€~‚„ƒ~|{|~zuv}€‚…‡‡…|vohfox~ƒ††‚€„ˆŠ‰‹ˆ€vmilruwuqoknqqrw€‡‰‰ˆƒ~{ƒ‡Š„ƒ†ˆ‹ŽŽŒ’“‘‹…„‚yonqw}ƒˆ†„„„‚~|wwy{xuvy}…‰ˆ‚}ywsolpw€…„‚~|~ƒ‰‘–”“ˆ{umloswxwtrqoqrrtw~…Š‹ˆ„ƒ‚ƒ‚ƒ„‰Œ‹ŽŽŒ‡„„…‚ztrsw}ƒ„„†‡‡‡…~}|xsuy}‚……ˆ…ysqnpprx~‚ƒ€}|}€†ŒŽŠ„}vtronqvy|}}|}|yyzvtz~‚„~‚‚†ˆ„„†ˆŠŠ’ŽŽ‹‡ˆˆ…‚~}{|~~€~ywy}„†€{wsuw|}~…‰‹‰ƒ|tpooonpw€†…‚€€‡Ž“–‘‡‚zrpihjkotwvy|}}|xx{}†ˆzzz~‚ƒ‰‰Šˆ‡‹ŒŠŒ‹‡‡ˆŠ‰‡ˆ„~yy~‚€|z{}}‚‡„€|xvvvx|‚„…†„€zwyywspmlqy‚‚{x|‡Ž’Œ†€{{xsppoosx{‚|uoqrz„ˆ‡†‚‚‚†‚‰‡{q}ƒ‹Œnzmy•‚zƒ‹“’£™Œƒ“œ…{Ž’Ÿ¨œ‘ŽytogzhkxŠ„|}vjgnb[W`X\`e}‚z|‚ˆ‡…–ž”‹ŽŒŽ‘Šˆ‰†Š~Žxim`\Zq}yagke[_gfbeefqalpy{|’™¢¬¬²¾Â»·ºº·µ«›~€Œ“™ˆ™–ƒ~‚ˆ„xq{xno__i]VX^d`[fgihmdibW\Xignol}ˆŠœŸ¡¢•€š¡©›”—‰xe^no‹s_u‚‚Š…x‚Ž|w†›Œ™¤™††”Œ–®À›“œ›‘ucv‡v|™–›˜“eXZdojx|mpuungND[]WYt{u{‚rmmwz‹Œƒ}rprlso‹}z†œ”‰Ÿ¡ˆŠ…u}“‘‘¤§¬–‰„‚‘€io‚|€”ƒ‰}k’«¡–“}„–ƒwƒœ«¤¥²µ™‰™†otsoƒ™†~qz~o{Œƒ|g[cgXaŠloxpqvˆ†„xjn~|kerŠ™|ivŽ˜ƒpkuzWIaaYRMc|}`;HSKAGo“Ÿ®²ÆÉ®•‘{y˜´ÇÕÄÆɸ¬œŒ—‰€ŽŒŽ‰…‹Œ}lhx€‘‰~†‹ˆdh|•—wxxxx]WZS=8Pf„‚~{xhWMQjvjvŒ{r†}mkcosqk__Zcj€›¡•Œ„Œ‘„mpq[T[u•›Šz€mYGLj¡­ÁÇ®¡š˜†ŠµÐ¨Ž’¡˜„u˜˜}z‹‹€„†‚zg\pw~}‰‹‘ž•”—€uŒqqƒ}nr’ƒvsp€Šzes„€‚“œ‰xyzvlSFESuZOr{w}u…yy› ’—£Œj_QekPOdr‚v[ht[Zw|y…««©­‘‚‚{iwr|©¾Á®®¿¹¬¥„‰yfzŠ‚fh}tqmYfmS?_“˜®»§Ž’¤›ver€|q‹¢…„‘‡eVj„…|}}}ph_XOGMGd”ªšygggnv‚ °­¿¯¡´ˆ`oŒ…qs€ ¦‰Œ~jc[ak^STk~vix|€‘–“‘‘~Šž˜Ž‰“’€ŒsUdtpbcmˆ’{yŒ•Žwal”—›­ •›}nˆxXUlw†šœ¦®’ƒxla\ey¢›‰‰†}mQ7APX\o~…¢©£œ†€€–¡•œ¢¤šœŸ~ntqhhipu|†w{„[9@VZ\WPjvfy—Œ€yŽ¦¦¢“Ÿ°©’¥±–—ˆms„„eq‚„ œ˜œ˜„ty{uu†›–“‡wyƒfTRXpƒŽƒ”}nXPj}nu‘•~~tep\CSlt{Ž™£¡‰ŒŠ}ypt“Ÿ— žž¬´‘t€zg`gv|yhfqqU9Oc`YNKcu_[–š £ ­²ž‘¢§Ÿ§»Áº°‹‚t`rwww{…†xlht{ulo‹¤˜€}ƒ‰„qdnjs„˜¢¢•tkoj^fy‘Ž†‡†‹…nVRQNWe©£†wv‚re‚ ˜• ¥²º§ˆ{}m]UY|‹vdfrkRUr|ˆ ¯¢Ž~chz†~{’¨šŠ¤™’‡’‘rcmqdYZ~–€[]pukSWxjhy¢¥‘‰Œ‰‚tu›­¢›¡¯Á¶‹€„{jlnuˆƒqmvbX[_‚•–™œ©¥‹oimgbf{Ž£’ˆ‚bV{|s}„Œ“‚]XdaQWcs„uo­”zs’³ ‹””ebr‹™ˆ–§¤˜yedcaVt•’”‹Ž‹jSZejjy‡˜œŽz~j^\‚£«¤¨µµ¤x~zqhl”ƒcbaXLV”•“«±°§†€‘‰x“˜˜’{‚vwv‚Ž|mu‹†nWV^XRRe{„yiap‰~ew¢›ŒŽœ›€dtŒ—žžªº¹‘ojrfSL_‡Žrp{qO8CURWdw¤¢†„p^t££¨·Ã¼›ˆ–’ƒ‚‘ «™}rlWOMr”¢˜†£nhpyudh„ž šƒ€Š‰sbˆ™‡xpˆ§›h_sseYUg|d[luwht§¼›ƒŠ—–rZ[p‚‡˜§®Ÿ„kde`Q\Š™’Š…ŒuOLdjellz–˜}|‹}dXqšžŒ‡¹¨‹ƒ’||†—ž™„oifI>b†‰‹•®¯†q–ˆƒ–§†nrvtrrˆ¤’wlq}`Wq{vonj}€iX`}‡x¡Á·’–‚[WosxŒ¡±¿ª‚nk_MRm“Ÿ¡ž–`B33@NUf€ŽŠ‰€zr]p™š’–›¬¼ ƒ—ˆ„‘“£ª„{dUTwœ‹ˆ” {q|znjnw–€{~rm—¤‚wrptlg|‚y{zt|yeRZabs«µº¬•”x`Zfigo€¥¸©•…xl\EW‘”†–¢¨£†mprVPgiwŒs~„thgyŽ‡z~‰Œ|~––‰~~Ž¤£ƒŠ‡pYc‚‡xxƒŒŠˆŽ…~Œ¦œ‡~€|ql¨—‹~…{bXYkjlhl|ƒ†ƒwifgca‹£™ £ž—‰lgqkku€Š«·„}xfUNt—~† šxTR][DE]q†Œ‹yƒ“…t{–’ŒŒ›—‰•Ÿ ™™••ž–„v}uhyƒ‚†{g\jvuw‰Ž˜~…yg‚œš’Š}{aRgzsv‚‡“—{bmq\P\€ ž”” ~eZ`jf_cx™©¯ž’‡p_s‡Ž}ˆ¦ s€wjbT]qk[\ex€qq“{u{‹€p}–“˜•˜ ’‰Š€…wm‡…z„‚ƒ‡z]g‡’¥¬¨¦Žwqx|xn}££™ŽuZ>GblhkˆŒ„tkssdd‚˜”ˆ§›zjwsiow~†Œ‚vmu“•‚ˆ”•”…onue^b[[ounlhco~€‘–‹‡~€‰li’Ÿ¤ª®³ž}rv‚~t„¤œ‡„˜…bJShut}–¤Ÿ‘{ovyz„›ŽŽoPTbj|‹‘£™ztkcjkf|ˆ–Ÿ ˜hmulgpzœ–‰…ˆ„wmz‹u|…œ˜|qƒrnpy…s\`geZ]q–¢‰†|zuaT^v‡›£¯Á³–{jltne§”“–¬ši\pzw‰›­À®‰€ƒvfXbˆ‘ww›“‚cFQURax•£œ†rqmdbg…ˆ…–ž¬­š€‚‚qu~{“™„xsx~€i| tgrŠ”wVm€wgXg‚h]ern`\‹²œ“‘Š~gby~€š«³Æħ“yfccYx¥‹„„‰ŒgFVipq}’«¸¡Š€}mXVy”…|„›§›{suolp}‰ž›|tymSV‡Œzw†Ÿ­šun{o_ew‚{sz‹Šrxš—„„{|‚nxˆ‹‰ƒwˆjMO]cZPx®´—ˆ—›„V?Xqhf~ ³³­¤•o^YuŒŒ’’•„{ww|†Š¦©—‰‡‡^S~šŽ‚ƒ˜¨œwk{cOVu‰‚‚‰ƒytaTw‹rv€Ž›€}”‘‡€y~~]exxto|ª¶•ƒ‹Œ{i[_oxzx„‰~nYag`g‹©²£¢|X]nqnp†ª·¢š˜cPe—”‚‘˜…oasqolq|†˜“š˜’ˆt`s…‹Œ—š€kˆvccrŽŽw|‹‚of[w}n|Š‹„pl{Œ‰ƒ~‡upuwupxœ§™™”w`htrvx†‹yqreU[WZ€“¦²¥œ~UGR_[]h…§«§¨ž’{^h”™‡œ¥£ˆow‹ƒxsu˜‹ƒˆ‹van‘‹v„Ž•‘{mxƒ€pdr~~‰‹…|l_m„ss†…€u„’ŒŽ‚Œrdeomb]ˆ°£ £¥™x\Uboklu„‘Œ{€pVHU|†“§²¨ŽtgcZ\ip}˜ž•‘•‰{c_‡“ˆˆ–˜’ƒnhoxzwvŠ“‘™•Œ€kzŽvƒ““}s™…ˆ‰v‚…neu{o`Ml’x†ƒpf_i|}…Œ–£ Ž}n^\^Yy§­¤¨¥¢~_duolj}ˆo\gqiH;d“…˜¦©•je`PYam~ŽšŸ—{r†‰}”žš£Ÿ„s|Š„‚‰‡u{…|v¡ ‹Š€€rp„†„‚}s€„tmmmd\l’„€Š…qbr‹˜œ°­}pcNBJo—š©·£‘‰eZX`ovv†—”‚h]``Qg‚Œ†• ¥™‰‡†sihkzyv}’‚z}•˜‰Œ’Ž‚vkqvy‡‹ƒrs{‚‹§¢–’“ƒ}sx†‹š“š‰vo__hn^p’Ž‡ŠwiqiXVjv‚Š‹¬¢{rfQJn”Œ¨¶«ž„wgm|€‚‚p^]]Q[~…‘†•™Ž†…~uob`y‚tkn†x„ª¨‘‘•~‡Œyz€z€Œ}nd^m{sv§¸¯¯ “‡va_ko{•˜›©“|rbW__n“Ž…“”nieecj“—ž©¡wlkcQ]Š“”›¤ž…rvvhacg}Œ„zmb`\p‰x{“›’‘Ž‹‰virnfXRb€„¬Ã³¨š‹…q`y†y}|{……ufe_htu›°ª®­£•k`ozu}Š™œ™‡nijbVe•Œ˜—eWX\TYt}‰›Ÿ›—‰rka`v‚ˆ”˜ £—~}Œ„uqx†ylpvpf\u“‹€€ˆŽƒvu€Š‚€zsu}o`cbcjv‰­¯§ ¡™…uelz†}…”•m]ZYZfš·±½½¯œf\\clw~‰›”‹|piabŠ–€ˆ’“†mZZbaYl„’…‰‹‰thz’~œš‹}uvrpz|ry€}ywgw•…}†}jq‡Œ‰’˜‘ˆo`_ZLU]`’±©µ¹žŒqjlhj†…€’‘†~yb[U\Ÿ• ¹½°”rmz‡•“‡††u^``eŠ˜†–›Š‡|mgdV]ijw„‡ˆŠ~€wqŠˆp~””…€…‚v€‰ˆ…~w{weq{o{¡˜†”yoh]jorŠ—‡sldVP\cwž´¶¥”‡{un}ŒŠˆ†‹xjqgLk”’˜¤œ¡¤‰ttmgxwt‹““mg`t™‡–‰sksfTfpnux…€~ˆ‰wŠ „„Œ’€|xx„xq~w{}uush€‘z|ˆ‹Œwdgvx{‹”››so[V[TS˜‘¢¯±®—|rlep{x‡˜•’–…ieYUwzx¥«¥£’Š…ut‰‘––•yus[cˆŒ†‹–becYX[h‚„y‚…qln„w‹˜—‹~‡‰‚Š“™š“ƒ{okjh]p“‹Œ”ŠˆƒaLYkmv…  „~|^KJM_„ˆ´¼¶£Ž}wwsw‡’˜š–‘~{kOIb|u‹  £™‰…„ohoyƒŒš‘zpyrŽš•ŒŽ{eorgfkx†~vntzocm–ˆ…ˆ”†srxop‚•qcd`e…€„“‘„oceu|{‰¢¡šqk`JDa{~“§¦­§‰zykk~|}•˜–‘}sfYoviv‘žŸ¡“‹‘Ž„€†ŽŠ†„‚ƒ}jl‘–†Ž˜–|gd\Sdsjl‚ˆwkjikfm“£˜š—Ž…tpuyˆŽŽœƒt_fk]m˜“…š“Œ†skgSKh{vŒ¤ ™ˆl_UH\|y{›©­­Œ…x{€Œ’‰‚xwwrizŠrv†‚Ž‹ˆˆˆ‡Œ€q|†}|„†‡z‘—…‡’š’~qrxg_mv~{uxqb[ehqŽ’˜²¬“‡|khifw…{ƒ˜–‚zvgeit”‡„‚ulszpgx’—”šœ|h\Y_kp„–“š “Š„roy~y†ˆˆ„uortƒƒxz„{‚‹Œ• Ÿ›Žƒumkvƒ‰—ž£¬¢Œy]WYY]lps~wqwTTcsƒ~¤ Ž|w}}‰–˜–’‘…rljgqˆˆ‘ Œ‰pnma\]`gŽˆ–˜qjdeocf~†Ž˜–—¢ ”‹ŒŒ‹‹„vmmqu‹“x‚Žwnjwƒvz”¡š’„Š~d]nz•˜‡¥ž–†ocghnz€‘~reef\Q_‰ˆ¦¤¨¥`mj]k|ƒ”›’”‘€lmlp…‚ƒ•œ–”ƒkolgqtoz‹Ž‰‚usmu|r|„~}x‚ˆ‚Š‰‰†‚…ƒmhsxš‘…†„~pdn‰‡”¢°¯–„‚{i^dk‰Ÿ•¢±«š†sjfUZfimyywyjeicZs‡|ˆšŸ¤›ƒv{p~Ž“‘…‚ˆ„wjw{€„}š‹}yog]XeqopzŒ‘‡ƒy{‚‰tix|„}rqŒ““œ›¡¢’}xqm_]l„—“˜™—‰jZ[itx{¦® —’„za\e…ˆ~Ž¬¤{‚…ufs‚ˆ†xyxqdXbp~r~–˜œŽ}u{e]ggr‘•|‚ˆŠ‡{k}Ÿ…n–¢™wj‡‡shr…Œydj{}tw|Ž›‡…‡pacsu€‰™¤y~€skgb†ŸŒŠ™š‚neyyw…‘¡©£–“†zlbe{ˆŠ“˜š{…labeswoo‰…qdakˆ}`|™˜‰‰…’™ƒu„‡˜ŠzqjwuƒŒ~Œ‡pw|qfeotxlo‚‰…‚yy›™z~ˆ„~tn}†‚‰”š¥¨‘€}sc\_kˆ“Ÿ£—…ndicXi…•žš¨°‘|ubhŒˆo‡žš–‘”qo{‡Œyv‚v^W^_€uaz—…~}“Šphnuz‡x‚‡~ww‡¥Œz†Ž‡uˆ‡xvyx|~hegns|‰¥—‚xgm‚shq€€‚‹…yndeˆ”}ˆ¢¥ntqduŽš›™¥Ÿ‚lqoxˆ‰•…„Ž—…plpvzon|‚{ned‡ebw„…{s‹ž„‡‘‰|vqmov{›œ‰…ˆ„yr|{srtwnqnmovy†”‡Ž†tc]q{sxžž§›…yghm{˜‹‘œ¢ˆkixq]Wg„Œ„†«¨ŒptŽrx†•}v‹š‚€{smljb_e~‡}{r€‰yiu‡†|qqzƒml†Š~tv‡§Ÿ„–’‚tt•ƒ€‚ˆƒqgeZTQg¦™•žž˜~\Vkfdah‚•”‚Š‘}or¥‹Žœšzt€ƒ|pw…˜™‡|‚†{~œ›‡„•Šzdi…‰}|„‰Šo_mttkgl…soƒ‰s`bsˆ‹‰‘œ—†qozvY]‹©™•›ž¢ŠbPg|zlm|ŠlciugUT|¨œŠŽ —„j^vzqq‚„›‹…‰|qq”Ÿ†„Ÿ‰lm~wg_cl|ys}—Š £…{ƒšufsŠŒš¡¢‡jgoi[Zk›’z{‹˜ŠcIe~|pz…”–xgs€{mft—•ˆ–£–€nz€x…•{mgedRNoš“£¥™w]sxj]m€…ƒpz‚‹|w”«Š†—’~sqƒ‚€z…„w€…‰{uƒ¥£†Œ’œš‚`l…~ux„™}klxybcj‡”~t}“Šq\^r}{|š ‚€lgrŠ‡{ˆ“£¡‹~ˆŠzsmsywgansn^bmˆŒz„ž‘}„}oi…‹‰…€ƒ‡ƒot‚‘’……Žœ’„†€sigrundq~†‡ƒŠ¢·›Ž”’’zp„€€’œ¢–€|vn`\\n|sv€Ž”ˆwopmjir|~y|…†ƒys{Œ{ƒŒ—œ‹{‚Ž‡}€Œ’’rpkeaWZu|y…”œ¥š‰‡ujbbgkmtz}ƒ‡ˆ™‹†Š‡…|ˆ‰€}€‰Ž‰{yux}zŒ œ—–”””‹~vpjnutz|~wuyƒ‹{ƒxoowtsrx‡‹†€x…ƒsm~Š|„˜™‹……ƒwsr|€yokonliy~€ƒ†‹‰€|yw{|„ƒ{pyyy|‘‘Š…‘‘‡{{}y|ˆˆsu{~z‚”Ÿžœ—ƒ€|xz|€…Œ‡ƒ†‡{ww~|rnswyvsrqqusqt}€~zxƒ‰‹‰„€‚}{{~|xwz~ƒ††ˆŠ‹ˆ„‚|wqouuxsszwrpnmrswx|‚Œ“”“‘’ˆ€~{~{†‚‹ˆˆ‰‚ŠŽŽˆ‹‰‡‰‰ˆˆƒ~zz|{wuttxzz}‚|‰ƒ‚†„ƒ}wzuwx{||~~ƒ~ˆ{…€€ƒ†‡{‚z|uvotuv{x~z~|ƒ|†„†‰Žˆ‚‚zz{vtrptsuwsx}}ƒƒ‹ƒ„†‚ƒ‰‰‰†ˆ‡Š‡Œ‰……†Š‡††ƒzyz||z{wy†„‚}|~~~„}zzzƒ|„‚~}u|~~z}|ƒ~…z|y|~wsytwy~ƒˆ‡|}y‚€…{xuzotqvy~ƒ…‹‰ƒ†…‚ƒ†……‰ˆŒˆŠˆˆˆ………„ˆŠƒˆƒ€{~†‡‡„ƒ…€†ƒ~}x~ws}{|}yuxt~x~|~x~w~x~x}€‡‚†‚†}‚~ƒ€x}x€z~|ƒ~}}|wt{zxzyzstsv|yxwz‚„„ˆ‹†€…„†ƒ………‰’ˆŠ‰‰Œ‰„††Œ€ƒ€ƒ{{~x~|ƒ„Šƒ‚‚yw}~€}z{xw|{xwz{yyyyz}ƒ~}~wxvxvz|}€~…†‚~„}……ˆ‡Š„…‚yxwu|tyrvtruquuy‚†Œ‰ƒ„„}‚}ƒŒ‹“–’ˆ‡„„‡‚‚‡„‚„|z‚„†‚ˆ„{zzyyz|„‚}~‚|€}€„ƒ‚}|~y{yzw}|€{yvu{u€~€|y{‡„‡„‡€€|zsutxxzzywz~yy~{z|y‚†‹‡†…~€‰ˆ‹Š‡ˆ‚‡ˆ…‡Š‹„†{|}~~|~ƒ€|x|yyuzv}~}‚~|ƒ|zxvyz}€‚ƒ}yyxu{|~‚€„ƒ„~{|x{†ƒ„~}~|€y}wyrqspuwz‰„Š„„|~~„ˆŽ“‘“Ž‰†„„…ˆˆŠ‡‹„†„„‚€‚€~}yxywxtyw|zz€|{yz|~y|‚„‚}|€…„‚~}yv{v{}{|}|zz{}||„‡‡†‚}y{{{{x{z|xwuuttw|‚†…ˆˆ†‚€}~€‚†‰Ž‹‰‰†……„‡„‡…‰ƒ†ƒ…„€~ƒƒ~~{z{x{~~~}‚ƒ€}|x|€‚€~~~|}|~zxwz|zyy|~{{|€~}~}|||€‚„ƒ‡„‚€}~{xwuwz{{z}{xwy}€€ƒ~„‡††‡‹ŒŽŽŒ‡„„……ƒƒƒ‚‚‚ƒ„‚€€‚€€€€~~€‚€~|}„ƒ€}yxvuw{}|yz{yvsw{}}~‚…†„„ƒ|{|~~€‚‚ƒ‚‚{yxwxxwwwwutwz}~‚ƒ„ƒ†‡‰ŠŒŽŽŠ‡…†ˆˆˆŠ‹‰‡„‚ƒƒ‚~|ywxz|€~}}|{xz~€}|~€€€|yyz|{{{|{xvxz|}{|~€~~~~‚‚‚…†ƒ€€}}{xvwvvutstuxz}€‚„„ƒƒ‚‚‚„ˆ‹ŒŒŽŽŒŠŠ‰‡…ƒ†‰‰ˆ†…„ƒ‚€~}||}}|ywx{{{{~€€~}}||~€‚ƒ„…ˆ‰‰…ƒ€~|zxwwvvtuvyyz|}}}|}~†‡…ƒ„…ƒ~€~}~{xw|xwvvxw|z||€€€}‚€ƒƒ†‡ˆ‰ˆŒŒ…‡‡ˆˆˆˆ…†ƒƒƒ„‚€€~~~}‚€€~}z{yy€|~y{}~~€€}„‡Š†ƒ|z{~€}zzyywz{xz}|~|~~~~‚€‚€ƒ€}{}~|}~„†‡„ƒ{{}|}~€~‚~}}~ƒ†……€ƒ†…„~€}€|€€‚ƒ„ƒ‚ƒ‚„ƒ†~€y{€z‚|‚||||}€ƒ„„~ƒ}€|~„‚}{xv|||‚‚‚~~}|}~€…ƒ†‡…}{y}{~€…†ƒ„ƒƒ€{zyƒ‚‚}yz}„‚€„€|~|‚}~€|€}ƒ††‚„„ƒ€~~~|}€€€‚€|~z~}}‚…ƒ„‚‚ƒ‚‚‚~€~|yvwy~€€€„„‚}€{}}„†ƒ~~~~||}}}€}€{{}€‚‚…ˆ…„ƒ€{}}~}}}‚€}}€€‚‚…~}{}€‚‚€|~‚€}€‚€}}zz|~ƒ†‡‡ƒ‚€}|}€€€~|yy|}}~~€€€|{|‚‚ƒƒƒ‚‚‚‚ƒ€}~}€€€~|{{}€‚„†„€}||}~||~‚‚‚~}‚‚€~~~}||}‚ƒ„„‚€|}€~}}|z{|{{~‚†‡ˆ‡…„ƒ„„„„„ƒ‚‚}zxz|€|zz{|{{~€€‚‚ƒ„…†ƒ€~|{|}‚~~€€‚„„‚}}~~~}€~|{|~‚€~}}}~€‚‚‚ƒƒ‚€‚‚‚€~}}|}~€‚ƒƒƒ„†…€~}}~}}€€€€~}{{~€‚‚€€~}|}‚‚‚‚€~~~€~}}~}zz|€€€~}~€€„ƒ~~~}|~~~ƒƒƒ‚€€€€€ƒ„ƒ€€~}}~€ƒ……ƒ€|yxy{}‚ƒƒƒƒ‚‚‚€~~€€€~}~€€~|{{|||}‚€~}}~€‚ƒ‚€€€€‚ƒ‚€~||}~~~}|{{{||~ƒ……‚€~~~€‚€‚ƒ„…„ƒ‚‚ƒ‚~{xvwxz}€„‡†„€~}}{{|~„…„~~}}}~}}||}}}€€€€€~€‚††…„€~}}~~}}‚‚€‚‚€€~}|||{|€€~|~€€€€€‚ƒ„„„‚€€€€~~~~|{zzz{{}€€‚‚‚„…„ƒ‚€€‚ƒ‚~|{|~~~}|}~€€}}~€‚„ƒ‚€€‚‚~~‚‚‚€~|{|}~ƒƒ€~|yx{ƒ„„„€|{{{}ƒ„„„‚~}~€€€€}{z{||‚‚€~||}~€‚„„ƒƒ‚€~€€€€€€~‚‚€~|~€‚ƒ‚€~~€‚ƒ‚‚‚~{zz|~€€€€}|{|~€€€}}~~~€‚€‚‚‚„„„}}~€‚ƒ‚€|zxxyzz|‚„„‚‚ƒƒƒ„„‚€}{{{~‚‚ƒ„‚€~}{xxz~€€€‚‚‚ƒƒ‚€~~€€~~~}}~~€‚……„~}}}}~‚„„„‚~{yyxz|~€‚‚ƒ„‚€€~~~~~€„‡†„ƒ}}|}}~~~}||{{|~€‚„„‚€€ƒ„„‚€‚€||~€~}||}~€€~~~~‚€€€€€|{z|~€„ˆ‹ˆ…€}|zxy{}„…„‚€€€‚ƒ„„ƒ€~~€€|{{||}}}}€‚ƒ‚€~}}~}{{|}}~€‚ƒ‚‚‚ƒ……„ƒ}}~~~|z{zxx{‚„…‡†„‚€~~€€~€€~}}€ƒ……„‚€~|{{{|~„ƒ‚€~}}~€ƒ„ƒƒƒ‚~}}}}||}€‚ƒƒ‚€~~~~}}~€€}|{{{|}ƒ„ƒ‚‚‚€~€€€~~~€€~}~€‚ƒƒ~||~~~~~€‚~}~€€€€‚‚ƒ„ƒƒ‚~|}}}}~€€€€€€‚‚€~}|{|}~~~}}~ƒƒ‚€€€~~~~~~|{}€‚ƒ‚‚ƒ„„ƒƒƒ‚~}}|{y{~€‚ƒ„„‚}||{{|}~}|}~~~€‚„†‡†„‚‚€€€€€‚‚‚}|||}~€‚‚€~~~}}‚‚‚€~}}||}~€„†‡†ƒ€}{zz{|}||||}}}~~€‚ƒƒ‚€€~}~‚„ƒ‚~{{}~~~~€€~~~~€€€‚‚‚ƒ‚€~~€‚„††ƒ|||}~~~€‚‚~~}||}~~€€€€~}€ƒ„ƒ‚€~~€~~~~}~~~€€€€€}|{zzz}ƒ†ˆ‡„}{{{}‚ƒƒ‚‚€€~}}}~~~ƒƒ‚‚‚ƒ‚€~~}{{}€‚ƒƒƒ‚‚‚€€€€€~~~~€~~~~||}€‚„…„ƒ€|zxz|~‚„ƒ}}|{zz|~€‚„„‚‚~~~~~€€‚‚€€~€€~|}}~€€€}~€€€€€€‚ƒ…†…ƒ‚€~}€€~~€‚‚‚€}{{|}~~}|}„…†………ƒ‚€~|{{|~€~~~~}{z{}~€‚„„‚|{{}‚ƒƒ€€€€€~|}~~~€€~€‚‚€}|}€‚ƒƒ‚€‚ƒ~|{|‚ƒ‚~~~~~}}|~€€~}|}ƒ„„ƒ‚€‚‚‚€€~|{|}~€€€€~|{{{{|~‚ƒƒ‚‚€~~}|}~€ƒ„‚~}|||‚~~€€‚‚~€}||}€„†‡†ƒ€~|{|~€€‚ƒƒ‚‚‚€~|{|~€}|}}~‚ƒ„ƒ„„‚€~~}}~‚‚~~|||~€ƒ„ƒ~}~~~}|||}~~‚‚€€€€~}||~~~~~~~}||}~‚„……„„ƒ‚‚‚‚‚‚‚‚€~}~~€€~~€€€€~~~‚‚‚‚€€‚ƒ‚€}{z{}€„ƒ‚}|{yxyz{|}‚‚‚‚‚‚ƒ„„ƒ€~~€‚~}}}}}}}~€€~||~€ƒ„„ƒ‚€~}}~‚ƒ„„„‚}~€€~}}}|}~€€‚‚~~~€€‚ƒ‚€~}||}~€€‚‚‚€€€~~~}}}|~~~~€‚‚€€~~~€€€€€~}~~~~€€‚‚€€€€~}|}~€€€~~‚ƒƒƒƒ‚~~€‚‚‚~}}}~€€~}}}}~€‚‚‚€}||{{{}ƒ‚~~€ƒ‚}}~~|||}€€~~}~~€‚‚€~~€‚‚€~}~€‚‚~~€‚€€€€€€€~~~}}}~€‚‚‚‚ƒ‚€€}{yz}‚‚€~~~~~~€€~~€€€}~€‚ƒƒ~{y{}€€€€€€€€‚„„ƒ~~~~|{||}€ƒƒ‚‚‚‚ƒƒƒƒ€~}}~~~}|}€€€€~€€‚‚€~}}~€}||}~~€€€‚}|~€ƒƒ€}||}~€€~~‚‚‚€€€€€‚|{|}€‚ƒƒ‚‚ƒ„ƒ~}||}~}||~ƒƒ„ƒ‚‚ƒƒ‚}|}}}~€‚‚€~}}}}}€€€€‚€}|{{|‚ƒƒƒƒ‚}||~€‚ƒ‚€€~|{|~€~}}~€‚‚‚ƒ‚€€~}}~€€€€€€ƒ……„}|{z|~€€~||}~€‚‚‚ƒƒƒ€~~€‚ƒƒ~}}~~€€€€~|{{}‚ƒƒ‚€€€}|}€€€€‚‚€~~~~~~~~~~}}€‚ƒƒ‚€‚ƒ‚~||~€€€€€€‚‚}}||}~€€€€€€€€€~~~€€€€€~~}~€‚‚€€€~|{|}€€€~}}~€ƒƒ‚€€€€€~~~~~~€€‚‚‚‚€~€€€€€€€€€~~~~€‚€~~~€€€€€~~~~€€€€€€€~~~~‚‚‚‚€~}}}~€€~~~~€€€€€€€€€€~~~€€€€€€€€€€€€€€~}}}~€‚ƒ‚€~}|}~€€€€€€€€~~~~~~€€€~~~€€€€€€~~~€€~}|}~~~€€€€€€€€€€€€~~~~€€€€€€€€€€€€€€€€€€€€€~~€€€~~~~€€€~~~~€€€€€€€€€€€€€€~~~~~€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~~€€€€~€€~~~€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€ \ No newline at end of file diff --git a/main/modes/system/intro/introMode.c b/main/modes/system/intro/introMode.c index 664402963..73f73d3ec 100644 --- a/main/modes/system/intro/introMode.c +++ b/main/modes/system/intro/introMode.c @@ -18,12 +18,18 @@ #include "embeddedOut.h" #include "bunny.h" +#define CUSTOM_INTRO_SOUND + static void introEnterMode(void); static void introExitMode(void); static void introMainLoop(int64_t elapsedUs); static void introBackgroundDrawCallback(int16_t x, int16_t y, int16_t w, int16_t h, int16_t up, int16_t upNum); static void introAudioCallback(uint16_t* samples, uint32_t sampleCnt); +#ifdef CUSTOM_INTRO_SOUND +static void introDacCallback(uint8_t* samples, int16_t len); +#endif + // static void introMenuCb(const char*, bool selected, uint32_t settingVal); static void introTutorialCb(tutorialState_t* state, const tutorialStep_t* prev, const tutorialStep_t* next, bool backtrack); @@ -263,9 +269,29 @@ swadgeMode_t introMode = { .fnEspNowRecvCb = NULL, .fnEspNowSendCb = NULL, .fnAdvancedUSB = NULL, - .fnDacCb = NULL, +#ifdef CUSTOM_INTRO_SOUND + .fnDacCb = introDacCallback, +#else + .fnDacCb = NULL, +#endif }; +#ifdef CUSTOM_INTRO_SOUND +typedef struct +{ + const uint8_t* sample; + size_t sampleCount; + + // Number of times each sample in the file needs to be played to match the audio sample rate + uint8_t factor; + + // Number of audio samples played + size_t progress; +} audioSamplePlayer_t; + +bool playSample(audioSamplePlayer_t* player, uint8_t* samples, int16_t len); +#endif + typedef struct { int16_t x; @@ -335,10 +361,8 @@ typedef struct #ifdef CUSTOM_INTRO_SOUND bool playingSound; - uint8_t* sound; - size_t soundSize; - size_t soundProgress; bool introComplete; + audioSamplePlayer_t samplePlayer; #endif menu_t* bgMenu; @@ -371,6 +395,11 @@ static void introEnterMode(void) loadFont("retro_logo.font", &iv->logoFont, true); makeOutlineFont(&iv->logoFont, &iv->logoFontOutline, true); +#ifdef CUSTOM_INTRO_SOUND + iv->samplePlayer.sample = cnfsGetFile("magfest.bin", &iv->samplePlayer.sampleCount); + iv->samplePlayer.factor = 2; +#endif + loadWsg("button_a.wsg", &iv->icon.button.a, true); loadWsg("button_b.wsg", &iv->icon.button.b, true); loadWsg("button_menu.wsg", &iv->icon.button.menu, true); @@ -441,9 +470,9 @@ static void introEnterMode(void) tutorialSetup(&iv->tut, introTutorialCb, buttonsSteps, ARRAY_SIZE(buttonsSteps), iv); #ifdef CUSTOM_INTRO_SOUND - iv->sound = NULL; - iv->playingSound = false; + iv->playingSound = true; iv->introComplete = false; + switchToSpeaker(); #endif // Load the MIDI file @@ -478,12 +507,6 @@ static void introExitMode(void) freeWsg(&iv->icon.touchGem); freeWsg(&iv->icon.swadge); -#ifdef CUSTOM_INTRO_SOUND - if (iv->sound != NULL) - { - heap_caps_free(iv->sound); - } -#endif unloadMidiFile(&iv->song); heap_caps_free(iv); @@ -498,44 +521,72 @@ static void introExitMode(void) */ static void introMainLoop(int64_t elapsedUs) { - // clearPxTft(); - #ifdef CUSTOM_INTRO_SOUND if (iv->playingSound || !iv->introComplete) { - int64_t songTime = (iv->soundSize * 1000000 + DAC_SAMPLE_RATE_HZ - 1) / DAC_SAMPLE_RATE_HZ; - int64_t animTime = (songTime * 2 / 3); - static int64_t timer = 0; - - shadeDisplayArea(0, 0, TFT_WIDTH, TFT_HEIGHT, 3, c234); + const paletteColor_t bgLineColors[] = {c444, c555}; + const paletteColor_t outlineCol = c025; + const paletteColor_t textCol = c025; - char title[64] = {0}; - char sub[64] = {0}; + paletteColor_t* screen = getPxTftFramebuffer(); + for (int i = 0; i < TFT_WIDTH; i++) + { + drawLineFast(i, 0, i, TFT_HEIGHT, bgLineColors[i % 2]); + } - strcat(title, "MAGFest"); - strcat(sub, "Swadge 2025"); - int64_t titleTicksPerChar = ((animTime) / strlen(title)); - int64_t subTicksPerChar = ((animTime) / strlen(sub)); + static int32_t timer = 0; - int16_t titleWidth = textWidth(&iv->logoFont, title); - int16_t titleX = (TFT_WIDTH - titleWidth) / 2; - int16_t titleY = (TFT_HEIGHT - iv->bigFont.height - iv->smallFont.height - 6) / 2; + const char mag[] = "MA"; + const char fest[] = "GFest"; - int16_t subWidth = textWidth(&iv->logoFont, sub); - int16_t subX = (TFT_WIDTH - subWidth) / 2; - int16_t subY = titleY + iv->bigFont.height + 5; + const char sub[] = "Sw"; + const char sub2[] = "adge"; - int titleLen = MIN(strlen(title), timer / titleTicksPerChar); - int subLen = MIN(strlen(sub), timer / subTicksPerChar); - // Trim the string - title[titleLen] = '\0'; - sub[subLen] = '\0'; + int16_t magW = textWidth(&iv->logoFont, mag); + int16_t festW = textWidth(&iv->logoFont, fest); + int16_t kernAG = -3; - drawText(&iv->logoFont, c555, title, titleX, titleY); - drawText(&iv->logoFont, c000, title, titleX - 1, titleY + 1); + int16_t titleWidth = magW + 1 + festW + kernAG; + int16_t titleX = (TFT_WIDTH - titleWidth) / 2; + int16_t titleY = (TFT_HEIGHT - iv->bigFont.height - iv->smallFont.height - 6) / 2; - drawText(&iv->logoFont, c555, sub, subX, subY); - drawText(&iv->logoFont, c000, sub, subX - 1, subY + 1); + int16_t magX = titleX; + int16_t festX = magX + magW + 1 + kernAG; + + int16_t subOneWidth = textWidth(&iv->logoFont, sub); + int16_t subTwoWidth = textWidth(&iv->logoFont, sub2); + int16_t kernWA = -8; + int16_t subWidth = textWidth(&iv->logoFont, sub) + textWidth(&iv->logoFont, sub2) + kernWA + 1; + int16_t subX = (TFT_WIDTH - subWidth) / 2; + int16_t subY = titleY + iv->bigFont.height + 5; + + // #define STRIPE_TEXT + + #ifdef STRIPE_TEXT + paletteColor_t colors[] = {c003, c003, c034, c003, c003, c003, c034, c003}; + int magColOffset = ((timer % 400000) / 100000); + int festColOffset = (magColOffset + (3 - magW % 4)) % 4; + drawTextMulticolored(&iv->logoFont, mag, magX, titleY, colors + magColOffset, 4, magW); + drawTextMulticolored(&iv->logoFont, fest, festX, titleY, colors + festColOffset, 4, festW); + #else + drawText(&iv->logoFont, textCol, mag, magX, titleY); + drawText(&iv->logoFont, textCol, fest, festX, titleY); + #endif + drawText(&iv->logoFontOutline, outlineCol, mag, magX, titleY); + drawText(&iv->logoFontOutline, outlineCol, fest, festX, titleY); + + #ifdef STRIPE_TEXT + int swColOffset = 3 - ((timer % 600000) / 150000); + int adgeColOffset = (swColOffset + (3 - (subOneWidth + kernWA) % 4)) % 4; + drawTextMulticolored(&iv->logoFont, sub, subX, subY, colors + swColOffset, 4, subOneWidth); + drawTextMulticolored(&iv->logoFont, sub2, subX + subOneWidth + kernWA, subY, colors + adgeColOffset, 4, + subTwoWidth); + #else + drawText(&iv->logoFont, textCol, sub, subX, subY); + drawText(&iv->logoFont, textCol, sub2, subX + subOneWidth + kernWA, subY); + #endif + drawText(&iv->logoFontOutline, outlineCol, sub, subX, subY); + drawText(&iv->logoFontOutline, outlineCol, sub2, subX + subOneWidth + kernWA, subY); timer += elapsedUs; @@ -544,6 +595,24 @@ static void introMainLoop(int64_t elapsedUs) { return; } + else + { + static int postTimerStart = 0; + if (postTimerStart == 0) + { + postTimerStart = timer; + } + + // Wait 2 seconds after sound ending before continuing + if ((timer - postTimerStart) >= 2000000) + { + iv->introComplete = true; + } + else + { + return; + } + } } #endif @@ -719,27 +788,38 @@ static void introBackgroundDrawCallback(int16_t x, int16_t y, int16_t w, int16_t } #ifdef CUSTOM_INTRO_SOUND -static void introDacCallback(uint8_t* samples, int16_t len) +bool playSample(audioSamplePlayer_t* player, uint8_t* samples, int16_t len) { - #define SPK_SILENCE (INT8_MIN) - if (iv->playingSound) + for (int i = 0; i < len; i++) { - if (iv->soundProgress + len >= iv->soundSize) + if (player->progress >= player->sampleCount * player->factor) { - size_t send = (iv->soundSize - iv->soundProgress); - memcpy(samples, iv->sound + iv->soundProgress, send); - memset(samples + send, SPK_SILENCE, len - send); - iv->playingSound = false; + // Set all to silence + memset(&samples[i], INT8_MIN, len - i); + return false; } else { - memcpy(samples, iv->sound + iv->soundProgress, len); - iv->soundProgress += len; + samples[i] = player->sample[player->progress / player->factor]; + player->progress++; + } + } + return player->progress < player->sampleCount * player->factor; +} + +static void introDacCallback(uint8_t* samples, int16_t len) +{ + if (iv->playingSound && iv->samplePlayer.sample) + { + iv->playingSound = playSample(&iv->samplePlayer, samples, len); + if (!iv->playingSound) + { + ESP_LOGI("Intro", "Finished playing sample"); } } else { - memset(samples, SPK_SILENCE, len); + globalMidiPlayerFillBuffer(samples, len); } } #endif diff --git a/main/modes/system/mainMenu/mainMenu.c b/main/modes/system/mainMenu/mainMenu.c index c4fe0b08f..86ed627f6 100644 --- a/main/modes/system/mainMenu/mainMenu.c +++ b/main/modes/system/mainMenu/mainMenu.c @@ -40,7 +40,6 @@ typedef struct { menu_t* menu; menuManiaRenderer_t* renderer; - font_t font_righteous; font_t font_rodin; midiFile_t fanfare; #ifdef SW_VOL_CONTROL @@ -157,7 +156,6 @@ static void mainMenuEnterMode(void) // Load a font loadFont("rodin_eb.font", &mainMenu->font_rodin, false); - loadFont("righteous_150.font", &mainMenu->font_righteous, false); // Load a song for when the volume changes #ifdef SW_VOL_CONTROL @@ -250,7 +248,6 @@ static void mainMenuExitMode(void) // Free the font freeFont(&mainMenu->font_rodin); - freeFont(&mainMenu->font_righteous); // Free the song #ifdef SW_VOL_CONTROL diff --git a/main/modes/test/keebTest/keebTest.c b/main/modes/test/keebTest/keebTest.c index 8b37cc62e..0256f8939 100644 --- a/main/modes/test/keebTest/keebTest.c +++ b/main/modes/test/keebTest/keebTest.c @@ -117,9 +117,9 @@ static const char* const colorSettingsOptions[] = {"Black", "Red", "Green", "Blue", "Yellow", "Magenta", "Cyan", "White", "Dk. gray", "Md. Gray", "Lt. Gray", "Pink", "Purple"}; -static const int32_t fontSettingsValues[] = {0, 1, 2, 3}; +static const int32_t fontSettingsValues[] = {0, 1, 2, 3, 4}; -static const char* const fontSettingsOptions[] = {"vga_ibm8", "radiostars", "rodin", "righteous"}; +static const char* const fontSettingsOptions[] = {"vga_ibm8", "radiostars", "rodin", "righteous", "retro_logo"}; static const int32_t typingModeSettingsValues[] = {0, 1, 2, 3}; @@ -163,6 +163,7 @@ static void keebEnterMode(void) loadFont("radiostars.font", &kbTest->fnt[1], false); loadFont("rodin_eb.font", &kbTest->fnt[2], false); loadFont("righteous_150.font", &kbTest->fnt[3], false); + loadFont("retro_logo.font", &kbTest->fnt[4], false); // Init Menu kbTest->menu = initMenu(keebTestName, kbMenuCb); diff --git a/main/modes/test/keebTest/keebTest.h b/main/modes/test/keebTest/keebTest.h index 460e3f762..63e90296b 100644 --- a/main/modes/test/keebTest/keebTest.h +++ b/main/modes/test/keebTest/keebTest.h @@ -45,7 +45,7 @@ typedef struct { // Assets wsg_t bg; - font_t fnt[4]; + font_t fnt[5]; // Vars State_t currState; From 7326d1f2cbaee756514d56b0b0111d10cce789ee Mon Sep 17 00:00:00 2001 From: Dylan Whichard Date: Wed, 15 Jan 2025 15:44:31 -0800 Subject: [PATCH 25/33] Smarter file preprocessing (#396) Assets are reprocessed when the source is newer than the destination --- tools/assets_preprocessor/src/bin_processor.c | 7 +- .../assets_preprocessor/src/chart_processor.c | 10 ++- tools/assets_preprocessor/src/fileUtils.c | 64 ++++++++++++++----- tools/assets_preprocessor/src/fileUtils.h | 1 + .../assets_preprocessor/src/font_processor.c | 17 ++++- .../assets_preprocessor/src/image_processor.c | 7 +- .../assets_preprocessor/src/json_processor.c | 13 ++-- tools/assets_preprocessor/src/raw_processor.c | 7 +- tools/assets_preprocessor/src/rmd_processor.c | 13 ++-- tools/assets_preprocessor/src/txt_processor.c | 7 +- 10 files changed, 105 insertions(+), 41 deletions(-) diff --git a/tools/assets_preprocessor/src/bin_processor.c b/tools/assets_preprocessor/src/bin_processor.c index e3b37405b..f24144329 100644 --- a/tools/assets_preprocessor/src/bin_processor.c +++ b/tools/assets_preprocessor/src/bin_processor.c @@ -14,11 +14,14 @@ void process_bin(const char* infile, const char* outdir) strcat(outFilePath, "/"); strcat(outFilePath, get_filename(infile)); - if (doesFileExist(outFilePath)) + if (!isSourceFileNewer(infile, outFilePath)) { - // printf("Output for %s already exists\n", infile); return; } + else if (doesFileExist(outFilePath)) + { + printf("[assets-preprocessor] %s modified! Regenerating %s\n", infile, get_filename(outFilePath)); + } /* Read input file */ FILE* fp = fopen(infile, "rb"); diff --git a/tools/assets_preprocessor/src/chart_processor.c b/tools/assets_preprocessor/src/chart_processor.c index 5fc350a7e..e08cc2217 100644 --- a/tools/assets_preprocessor/src/chart_processor.c +++ b/tools/assets_preprocessor/src/chart_processor.c @@ -25,10 +25,14 @@ void process_chart(const char* infile, const char* outdir) char* dotptr = strrchr(outFilePath, '.'); snprintf(&dotptr[1], strlen(dotptr), "cch"); - if (doesFileExist(outFilePath)) + if (!isSourceFileNewer(infile, outFilePath)) { - // printf("Output for %s already exists\n", infile); - // return; + //printf("Output for %s already exists and is up-to-date\n", infile); + return; + } + else if (doesFileExist(outFilePath)) + { + printf("[assets-preprocessor] %s modified! Regenerating %s\n", infile, get_filename(outFilePath)); } charSection_t section = CS_NONE; diff --git a/tools/assets_preprocessor/src/fileUtils.c b/tools/assets_preprocessor/src/fileUtils.c index 2dcf6c22a..4c052304c 100644 --- a/tools/assets_preprocessor/src/fileUtils.c +++ b/tools/assets_preprocessor/src/fileUtils.c @@ -1,11 +1,12 @@ +#include "fileUtils.h" + #include #include #include #include #include #include - -#include "fileUtils.h" +#include /** * TODO @@ -31,21 +32,7 @@ long getFileSize(const char* fname) */ bool doesFileExist(const char* fname) { - int fd = open(fname, O_CREAT | O_WRONLY | O_EXCL, S_IRUSR | S_IWUSR); - if (fd < 0) - { - /* failure */ - if (errno == EEXIST) - { - /* the file already existed */ - close(fd); - return true; - } - } - - /* File does not exist */ - close(fd); - return false; + return 0 == access(fname, F_OK); } /** @@ -62,4 +49,47 @@ const char* get_filename(const char* filename) return ""; } return slash + 1; +} + + +/** + * @brief Returns true if the file `sourceFile` has a last-modified time after that of `destFile`, or if `destFile` does not exist. + * + * @param sourceFile The path to the "source" file, from which `destFile` is generated + * @param destFile The path to the "destination" file path, which should be regenerated if older than `sourceFile`. + * @return true sourceFile was modified after destFile, so destFile should be updated + * @return false destFile was modified after sourceFile, so destFile does not need to be updated + */ +bool isSourceFileNewer(const char* sourceFile, const char* destFile) +{ + + long long srcMtime = 0; + long long destMtime = 0; + + // Just use stat() + struct stat statVal = {0}; + errno = 0; + int statResult = stat(sourceFile, &statVal); + if (statResult == 0) + { + srcMtime = statVal.st_mtime; + } + else if (errno != ENOENT) + { + fprintf(stderr, "Cannot stat() file %s: %s (%d)\n", sourceFile, strerror(errno), errno); + } + + memset(&statVal, 0, sizeof(struct stat)); + errno = 0; + statResult = stat(destFile, &statVal); + if (statResult == 0) + { + destMtime = statVal.st_mtime; + } + else if (errno != ENOENT) + { + fprintf(stderr, "Cannot stat() file %s: %s (%d)\n", destFile, strerror(errno), errno); + } + + return srcMtime > destMtime; } \ No newline at end of file diff --git a/tools/assets_preprocessor/src/fileUtils.h b/tools/assets_preprocessor/src/fileUtils.h index f179d5745..02305ea76 100644 --- a/tools/assets_preprocessor/src/fileUtils.h +++ b/tools/assets_preprocessor/src/fileUtils.h @@ -11,5 +11,6 @@ long getFileSize(const char* fname); bool doesFileExist(const char* fname); const char* get_filename(const char* filename); +bool isSourceFileNewer(const char* sourceFile, const char* destFile); #endif \ No newline at end of file diff --git a/tools/assets_preprocessor/src/font_processor.c b/tools/assets_preprocessor/src/font_processor.c index 493127784..0a85a82be 100644 --- a/tools/assets_preprocessor/src/font_processor.c +++ b/tools/assets_preprocessor/src/font_processor.c @@ -79,9 +79,6 @@ void appendCharToFile(FILE* fp, unsigned char* data, int w, int h, int charStart */ void process_font(const char* infile, const char* outdir) { - /* Load the font PNG */ - int w, h, n; - unsigned char* data = stbi_load(infile, &w, &h, &n, 4); /* Open up the output file */ char outFilePath[128] = {0}; @@ -90,6 +87,20 @@ void process_font(const char* infile, const char* outdir) strcat(outFilePath, get_filename(infile)); /* Clip off the ".png", leaving ".font" */ *(strrchr(outFilePath, '.')) = 0; + + if (!isSourceFileNewer(infile, outFilePath)) + { + return; + } + else if (doesFileExist(outFilePath)) + { + printf("[assets-preprocessor] %s modified! Regenerating %s\n", infile, get_filename(outFilePath)); + } + + /* Load the font PNG */ + int w, h, n; + unsigned char* data = stbi_load(infile, &w, &h, &n, 4); + FILE* fp = fopen(outFilePath, "wb+"); int charsWritten = 0; diff --git a/tools/assets_preprocessor/src/image_processor.c b/tools/assets_preprocessor/src/image_processor.c index 203c46427..a56a47c02 100644 --- a/tools/assets_preprocessor/src/image_processor.c +++ b/tools/assets_preprocessor/src/image_processor.c @@ -136,11 +136,14 @@ void process_image(const char* infile, const char* outdir) char* dotptr = strrchr(outFilePath, '.'); snprintf(&dotptr[1], strlen(dotptr), "wsg"); - if (doesFileExist(outFilePath)) + if (!isSourceFileNewer(infile, outFilePath)) { - // printf("Output for %s already exists\n", infile); return; } + else if (doesFileExist(outFilePath)) + { + printf("[assets-preprocessor] %s modified! Regenerating %s\n", infile, get_filename(outFilePath)); + } /* Load the source PNG */ int w, h, n; diff --git a/tools/assets_preprocessor/src/json_processor.c b/tools/assets_preprocessor/src/json_processor.c index 4b0ad36a1..34b8d170a 100644 --- a/tools/assets_preprocessor/src/json_processor.c +++ b/tools/assets_preprocessor/src/json_processor.c @@ -25,11 +25,14 @@ void process_json(const char* infile, const char* outdir) snprintf(&dotptr[1], strlen(dotptr), "hjs"); #endif - // if(doesFileExist(outFilePath)) - // { - // printf("Output for %s already exists\n", infile); - // return; - // } + if (!isSourceFileNewer(infile, outFilePath)) + { + return; + } + else if (doesFileExist(outFilePath)) + { + printf("[assets-preprocessor] %s modified! Regenerating %s\n", infile, get_filename(outFilePath)); + } /* Read input file */ FILE* fp = fopen(infile, "rb"); diff --git a/tools/assets_preprocessor/src/raw_processor.c b/tools/assets_preprocessor/src/raw_processor.c index e954a6986..e2f9e9e27 100644 --- a/tools/assets_preprocessor/src/raw_processor.c +++ b/tools/assets_preprocessor/src/raw_processor.c @@ -20,11 +20,14 @@ void process_raw(const char* inFile, const char* outDir, const char* outExt) char* dotPtr = strrchr(outFilePath, '.'); strncpy(&dotPtr[1], outExt, sizeof(outFilePath) - (dotPtr - outFilePath) - 1); - if (doesFileExist(outFilePath)) + if (!isSourceFileNewer(inFile, outFilePath)) { - // printf("Output for %s already exists\n", inFile); return; } + else if (doesFileExist(outFilePath)) + { + printf("[assets-preprocessor] %s modified! Regenerating %s\n", inFile, get_filename(outFilePath)); + } // Read input file const char* errdesc = NULL; diff --git a/tools/assets_preprocessor/src/rmd_processor.c b/tools/assets_preprocessor/src/rmd_processor.c index 37993bd40..5cda30f3e 100644 --- a/tools/assets_preprocessor/src/rmd_processor.c +++ b/tools/assets_preprocessor/src/rmd_processor.c @@ -20,11 +20,14 @@ void process_rmd(const char* infile, const char* outdir) char* dotptr = strrchr(outFilePath, '.'); snprintf(&dotptr[1], strlen(dotptr), "rmh"); - // if(doesFileExist(outFilePath)) - // { - // printf("Output for %s already exists\n", infile); - // return; - // } + if(!isSourceFileNewer(infile, outFilePath)) + { + return; + } + else if (doesFileExist(outFilePath)) + { + printf("[assets-preprocessor] %s modified! Regenerating %s\n", infile, get_filename(outFilePath)); + } /* Read input file */ FILE* fp = fopen(infile, "rb"); diff --git a/tools/assets_preprocessor/src/txt_processor.c b/tools/assets_preprocessor/src/txt_processor.c index fe6e13575..085d50867 100644 --- a/tools/assets_preprocessor/src/txt_processor.c +++ b/tools/assets_preprocessor/src/txt_processor.c @@ -41,11 +41,14 @@ void process_txt(const char* infile, const char* outdir) strcat(outFilePath, "/"); strcat(outFilePath, get_filename(infile)); - if (doesFileExist(outFilePath)) + if (!isSourceFileNewer(infile, outFilePath)) { - // printf("Output for %s already exists\n", infile); return; } + else if (doesFileExist(outFilePath)) + { + printf("[assets-preprocessor] %s modified! Regenerating %s\n", infile, get_filename(outFilePath)); + } /* Read input file */ FILE* fp = fopen(infile, "rb"); From 160e6e950cdac5740c8ac0c4ab9fb98e49440fd8 Mon Sep 17 00:00:00 2001 From: Dylan Whichard Date: Wed, 15 Jan 2025 23:38:27 -0800 Subject: [PATCH 26/33] Don't erase TTT CPU difficulty (#398) --- main/modes/games/ultimateTTT/ultimateTTTgame.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/main/modes/games/ultimateTTT/ultimateTTTgame.c b/main/modes/games/ultimateTTT/ultimateTTTgame.c index f9f1eb16e..e72793a48 100644 --- a/main/modes/games/ultimateTTT/ultimateTTTgame.c +++ b/main/modes/games/ultimateTTT/ultimateTTTgame.c @@ -100,8 +100,10 @@ void tttBeginGame(ultimateTTT_t* ttt) memset(ttt->game.cellTimers, 0, sizeof(ttt->game.cellTimers)); memset(ttt->game.gameTimers, 0, sizeof(ttt->game.gameTimers)); - /// Clear any CPU data + /// Clear any CPU data, preserving difficulty + tttCpuDifficulty_t origDiff = ttt->game.cpu.difficulty; memset(&ttt->game.cpu, 0, sizeof(ttt->game.cpu)); + ttt->game.cpu.difficulty = origDiff; // Show the game UI tttShowUi(TUI_GAME); From 0cf81b5f09caa6d71a147857d4fdcdea24e65e52 Mon Sep 17 00:00:00 2001 From: Dylan Whichard Date: Fri, 17 Jan 2025 15:12:57 -0800 Subject: [PATCH 27/33] Minor makefile fixes (#403) * Fix plantuml.jar always re-downloading * Default to using clang-format-17 --- makefile | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/makefile b/makefile index 210a27183..7caf3759d 100644 --- a/makefile +++ b/makefile @@ -36,9 +36,9 @@ ifeq ($(HOST_OS),Windows) endif # clang-format may actually be clang-format-17 -CLANG_FORMAT:=clang-format +CLANG_FORMAT:=clang-format-17 ifeq (, $(shell which $(CLANG_FORMAT))) - CLANG_FORMAT:=clang-format-17 + CLANG_FORMAT:=clang-format endif ifeq ($(HOST_OS),Linux) @@ -397,8 +397,10 @@ fullclean: clean # Utility targets ################################################################################ -docs: +plantuml.jar: -wget -nc -O plantuml.jar https://github.com/plantuml/plantuml/releases/download/v1.2023.4/plantuml-1.2023.4.jar + +docs: plantuml.jar doxygen ./Doxyfile format: From 646bed077355a8229180ae8b319d459366946014 Mon Sep 17 00:00:00 2001 From: Dylan Whichard Date: Sat, 18 Jan 2025 15:55:55 -0800 Subject: [PATCH 28/33] Fix TTT Hard AI Subgame Selection (#401) * Well that would certainly explain it * Check all valid moves for wins when deciding subgame (hard) * Clean up subgame selection (hard) * Formatting * Fix subgame marker box not being reset * Clean up old loop remnants * Fix ridiculous comment * Formatting --- main/modes/games/pango/paSoundManager.c | 2 +- .../games/ultimateTTT/ultimateTTTcpuPlayer.c | 160 +++++++++++++++++- .../modes/games/ultimateTTT/ultimateTTTgame.c | 2 + main/modes/utilities/gamepad/gamepad.c | 2 +- 4 files changed, 156 insertions(+), 10 deletions(-) diff --git a/main/modes/games/pango/paSoundManager.c b/main/modes/games/pango/paSoundManager.c index 96cb2316c..8cc20df39 100644 --- a/main/modes/games/pango/paSoundManager.c +++ b/main/modes/games/pango/paSoundManager.c @@ -60,7 +60,7 @@ bool pa_setBgm(paSoundManager_t* self, uint16_t newBgmIndex) // All BGM's are intended to loop! midiPlayer_t* player = globalMidiPlayerGet(MIDI_BGM); player->loop = true; - + if (self->currentBgmIndex == newBgmIndex) { return false; diff --git a/main/modes/games/ultimateTTT/ultimateTTTcpuPlayer.c b/main/modes/games/ultimateTTT/ultimateTTTcpuPlayer.c index 57615dad2..4a24984d3 100644 --- a/main/modes/games/ultimateTTT/ultimateTTTcpuPlayer.c +++ b/main/modes/games/ultimateTTT/ultimateTTTcpuPlayer.c @@ -67,6 +67,19 @@ static const char strMoveEmptySide[] = "Empty Side"; #define DECODE_LOC_Y(r) (((r) >> 5) / 3) #define DECODE_MOVE(r) ((r) & 0x1F) +typedef struct +{ + int subX, subY, cellX, cellY; +} move_t; + +typedef struct +{ + bool winsGame; + bool losesGame; + move_t bestOpponentMove; + int movesToEnd; +} moveAnalysis_t; + static bool selectSubgame_easy(ultimateTTT_t* ttt, int* x, int* y); static bool selectSubgame_medium(ultimateTTT_t* ttt, int* x, int* y); static bool selectSubgame_hard(ultimateTTT_t* ttt, int* x, int* y); @@ -85,6 +98,9 @@ static rowCount_t checkDiag(const tttPlayer_t game[3][3], int n, tttPlayer_t pla static uint16_t movesToWin(const tttPlayer_t subgame[3][3], tttPlayer_t player); static uint16_t analyzeSubgame(const tttPlayer_t subgame[3][3], tttPlayer_t player, uint16_t filter); +static bool analyzeMove(const tttSubgame_t subgames[3][3], const move_t* move, tttPlayer_t player, + moveAnalysis_t* result); + const char* getMoveName(uint16_t move); void printGame(const tttPlayer_t subgame[3][3]); @@ -308,29 +324,91 @@ static bool selectSubgame_medium(ultimateTTT_t* ttt, int* x, int* y) static bool selectSubgame_hard(ultimateTTT_t* ttt, int* x, int* y) { - // Eventually this will become the medium difficulty once I get the actual difficult AI working - // Here's what to do: // - Construct a 'subgame' to match the main board (which we can modify if needed to simulate stuff) and also to // avoid rewriting the algorithm // - Its board will have all the completed subgames marked with the winner, and the rest as NONE (TODO: how to // handle a tie??? Just use the TIE player? Yeah probably) // - We calculate the next possible move on that board, and then take a look at the board we'd need to win to get // our marker there - // - So... I guess we should take all the possible moves in that board, and then what... + // But also check every valid move for a game-winning move, and try that first + + // Who are we? + tttPlayer_t cpuPlayer = (ttt->game.singlePlayerPlayOrder == GOING_FIRST) ? TTT_P2 : TTT_P1; + + // To avoid a second pass + int32_t maxScore = INT32_MIN; + move_t maxMove = {0}; + + // The whole game turned into a subgame tttPlayer_t subgame[3][3]; for (int gx = 0; gx < 3; gx++) { for (int gy = 0; gy < 3; gy++) { subgame[gx][gy] = ttt->game.subgames[gx][gy].winner; + + if (subgame[gx][gy] == TTT_NONE) + { + for (int cx = 0; cx < 3; cx++) + { + for (int cy = 0; cy < 3; cy++) + { + int score = INT32_MIN; + + move_t move = { + .subX = gx, + .subY = gy, + .cellX = cx, + .cellY = cy, + }; + moveAnalysis_t analysis = {0}; + + if (analyzeMove(ttt->game.subgames, &move, cpuPlayer, &analysis)) + { + if (analysis.winsGame) + { + score = INT32_MAX; + } + else if (analysis.losesGame) + { + score = INT32_MIN + 1; + } + else + { + score = 1; + } + } + else + { + // Impossible move, give it minimum score possible + score = INT32_MIN; + } + + if (score > maxScore) + { + maxScore = score; + maxMove.subX = gx; + maxMove.subY = gy; + maxMove.cellX = cx; + maxMove.cellY = cy; + } + } + } + } } } - tttPlayer_t cpuPlayer = (ttt->game.singlePlayerPlayOrder == GOING_FIRST) ? TTT_P2 : TTT_P1; - uint16_t mainResult = analyzeSubgame(subgame, cpuPlayer, 0); - uint16_t mainMove = DECODE_MOVE(mainResult); - int mainX = DECODE_LOC_X(mainResult); - int mainY = DECODE_LOC_Y(mainResult); + uint16_t mainResult = analyzeSubgame(subgame, cpuPlayer, 0); + uint16_t mainMove = DECODE_MOVE(mainResult); + int mainX = DECODE_LOC_X(mainResult); + int mainY = DECODE_LOC_Y(mainResult); + + // Override the best subgame if there's a winning move + if (maxScore == INT32_MAX) + { + mainX = maxMove.subX; + mainY = maxMove.subY; + } if (WON != mainMove && mainMove) { @@ -1102,6 +1180,72 @@ static uint16_t analyzeSubgame(const tttPlayer_t subgame[3][3], tttPlayer_t play return 0; } +static bool analyzeMove(const tttSubgame_t subgames[3][3], const move_t* move, tttPlayer_t player, + moveAnalysis_t* result) +{ + tttSubgame_t board[3][3]; + if (subgames[move->subX][move->subY].winner != TTT_NONE + || subgames[move->subX][move->subY].game[move->cellX][move->cellY] != TTT_NONE) + { + // Impossible move! + return false; + } + + memcpy(board, subgames, sizeof(board)); + + const tttPlayer_t opponent = (player == TTT_P1) ? TTT_P2 : TTT_P1; + tttPlayer_t turn = player; + move_t lastMove = {0}; + memcpy(&lastMove, move, sizeof(move_t)); + int moveNum = 0; + + board[lastMove.subX][lastMove.subY].game[lastMove.cellX][lastMove.cellY] = player; + + // Check if that wins the cell and update it accordingly + tttPlayer_t subgameWinner = tttCheckWinner(board[lastMove.subX][lastMove.subY].game); + if (subgameWinner != TTT_NONE) + { + board[lastMove.subX][lastMove.subY].winner = subgameWinner; + } + + // Get the overall board to check the game's status + tttPlayer_t bigBoard[3][3]; + for (int gx = 0; gx < 3; gx++) + { + for (int gy = 0; gy < 3; gy++) + { + bigBoard[gx][gy] = board[gx][gy].winner; + } + } + + tttPlayer_t winner = tttCheckWinner(bigBoard); + if (winner == player) + { + result->winsGame = true; + result->losesGame = false; + result->movesToEnd = moveNum; + memset(&result->bestOpponentMove, 0, sizeof(move_t)); + return true; + } + else if (winner == opponent) + { + result->winsGame = false; + result->losesGame = true; + result->movesToEnd = moveNum; + memcpy(&result->bestOpponentMove, &lastMove, sizeof(move_t)); + return true; + } + else + { + // TTT_NONE + result->winsGame = false; + result->losesGame = false; + return true; + } + + return false; +} + const char* getMoveName(uint16_t move) { switch (move) diff --git a/main/modes/games/ultimateTTT/ultimateTTTgame.c b/main/modes/games/ultimateTTT/ultimateTTTgame.c index e72793a48..5f100b80f 100644 --- a/main/modes/games/ultimateTTT/ultimateTTTgame.c +++ b/main/modes/games/ultimateTTT/ultimateTTTgame.c @@ -99,6 +99,8 @@ void tttBeginGame(ultimateTTT_t* ttt) ttt->game.moveAnimTimer = MOVE_ANIM_TIME; memset(ttt->game.cellTimers, 0, sizeof(ttt->game.cellTimers)); memset(ttt->game.gameTimers, 0, sizeof(ttt->game.gameTimers)); + memset(ttt->game.priorCellAnim, 0, sizeof(ttt->game.priorCellAnim)); + memset(ttt->game.nextSubgameAnim, 0, sizeof(ttt->game.nextSubgameAnim)); /// Clear any CPU data, preserving difficulty tttCpuDifficulty_t origDiff = ttt->game.cpu.difficulty; diff --git a/main/modes/utilities/gamepad/gamepad.c b/main/modes/utilities/gamepad/gamepad.c index e37e9717d..941f0258b 100644 --- a/main/modes/utilities/gamepad/gamepad.c +++ b/main/modes/utilities/gamepad/gamepad.c @@ -294,7 +294,7 @@ void gamepadEnterMode(void) addSingleItemToMenu(gamepad->menu, str_exit); // Initialize menu renderer - gamepad->renderer = initMenuManiaRenderer(NULL, NULL, NULL); + gamepad->renderer = initMenuManiaRenderer(NULL, NULL, NULL); gamepad->renderer->ledsOn = false; // Set up the IMU From 8066433caba91b42223ad5b3eb0903db17ed477d Mon Sep 17 00:00:00 2001 From: Dylan Whichard Date: Sat, 18 Jan 2025 15:56:20 -0800 Subject: [PATCH 29/33] Configurable joystick (#402) * Make joystick device configurable * Fix selection on windows * Fix joystick connection logic not working properly with linux file descriptors * Add joystick connection command * Joystick docs * Formatting * More joystick docs * Fix help formatting * Add util functions for converting between buttonBit_t and strings * Use button parsing functions * Make joystick input fully configurable, why not * Add functions for joystick config * Add presets * Add deadzone for touchpad axis * Add joystick preset command * Fix broken markdown table and add joystick command docs --- docs/EMULATOR.md | 68 ++- emulator/src/emu_utils.c | 43 ++ emulator/src/emu_utils.h | 3 + emulator/src/extensions/emu_args.c | 32 ++ emulator/src/extensions/emu_args.h | 6 + emulator/src/extensions/emu_ext.c | 12 +- emulator/src/extensions/gamepad/ext_gamepad.c | 527 ++++++++++++++---- emulator/src/extensions/gamepad/ext_gamepad.h | 18 + emulator/src/extensions/replay/ext_replay.c | 33 +- .../src/extensions/tools/emu_console_cmds.c | 295 +++++++++- 10 files changed, 876 insertions(+), 161 deletions(-) diff --git a/docs/EMULATOR.md b/docs/EMULATOR.md index afcd66113..d76d2ed1f 100644 --- a/docs/EMULATOR.md +++ b/docs/EMULATOR.md @@ -69,6 +69,43 @@ the `install.sh` script: | F12 | Screeshot | Saves a screenshot to a PNG file | +## Joystick Input + +The Windows and Linux versions of the emulator support Joystick input. If a Joystick device is available, +the emulator will automatically connect to it on startup. The `--joystick` command-line argument can be +used to connect to a specific device, or the `joystick` console command can be used to disconnect from +the current joystick device, reconnect to the previous joystick device, or to connect to a different +joystick device at any time. + +The joystick input uses the same configuration as the Swadge's Computer Gamepad mode. Other joysticks +may require configuration. + +### Joystick Mapping + +Axis Mapping + +| Axis | Description | +|----------------|---------------------------------------| +| 0 (X axis) | The horizontal axis of the touchpad | +| 1 (Y axis) | The vertical axis of the touchpad | +| 2 (Z axis) | Not used | +| 3 (X rotation) | The Y component of the accelerometer | +| 4 (Y rotation) | The X component of the accelerometer | +| 5 (Z rotation) | The Z component of the accelerometer | +| 6 (POV Hat X) | The horizontal component of the D-pad | +| 7 (POV Hat Y) | The vertical component of the D-pad | + +Button Mapping + +| Button | Description | +|--------|----------------------| +| 1 | A Button | +| 2 | B Button | +| 3-10 | Not used | +| 11 | Menu Button (Select) | +| 12 | Pause Button (Start) | +| 13-32 | Not used | + ## Command-line Arguments The emulator supports a variety of command-line arguments that can enable extra functionality or @@ -89,6 +126,8 @@ Emulates a swadge --fuzz-motion[=y|n] Set whether motion inputs are fuzzed --headless Runs the emulator without a window. --hide-leds Don't draw simulated LEDs next to the display + -j, --joystick=JOYDEV Sets the joystick device to use. + --js-preset=PRESET Sets the joystick config preset to use. PRESET can be swadge or switch -k, --keymap=LAYOUT Use an alternative keymap. LAYOUT can be azerty, colemak, or dvorak -l, --lock Lock the emulator in the start mode --midi-file=FILE Open and immediately play a MIDI file @@ -246,6 +285,14 @@ from one system will not necessarily produce the same output if it is used on a `--keymap`: Specify an alternative keyboard layout to use for mapping emulator inputs. Possible options are `azerty`, `dvorak`, or `colemak`. +`--joystick`: Specifies the Joystick device to connect to. The value passed to this argument will depend on +the platform; on Windows the value should be a number from 0 to 15, with 0 being the first connected joystick +and so on; on Linux the value should be the path to a Joystick device, such as `/dev/input/js0`. Joystick +input is not yet supported on Mac. + +`--js-preset`: Specifies the Joystick configuration preset to use. Possible values are `swadge` (the default), +and `switch`. + ## Console Commands The emulator supports a small number of commands in the console, which can be opened by pressing `F4` or @@ -261,15 +308,22 @@ type `help ` into the console. | `gif [filename]` | Starts recording a GIF to `filename` (or a timestamp-based file name), or stops the current recording | | `replay ` | Starts playing back inputs from `filename`. Stops any current playing back or recording of inputs. | | `record [filename]` | Starts recording inputs to `filename`, or to a timestamp-based file name if no filename is given | -| `fuzz [on|off]` | Toggles fuzzing on or off | -| `fuzz buttons [on|off]` | Toggles fuzzing of button presses on or off | +| fuzz [on\|off] | Toggles fuzzing on or off | +| fuzz buttons [on\|off] | Toggles fuzzing of button presses on or off | | `fuzz buttons mask <...>`| Sets the buttons that will be used when fuzzing, separated by spaces, e.g. `fuzz buttons mask up down left right` | | `fuzz buttons mask` | Prints the buttons that will be used when fuzzing | -| `fuzz touch [on|off]` | Toggles fuzzing of touchpad inputs on or off | -| `fuzz motion [on|off]` | Toggles fuzzing of accelerometer motion on or off | -| `fuzz time [on|off]` | Toggles fuzzing of frame timings on or off | -| `touchpad [on|off]` | Toggles the emulator's virtual touchpad on or off | -| `leds [on|off]` | Toggles the emulator's virtual LEDs on or off | +| fuzz touch [on\|off] | Toggles fuzzing of touchpad inputs on or off | +| fuzz motion [on\|off] | Toggles fuzzing of accelerometer motion on or off | +| fuzz time [on\|off] | Toggles fuzzing of frame timings on or off | +| joystick [on\|off] | Toggles the joystick on or off | +| `joystick device ` | Connects to a specific joystick device | +| `joystick map button [ ...]` | Maps joystick button numbers to emulator buttons | +| `joystick map touchpad ` | Maps two joystick axes onto the emulator touchpad | +| `joystick map motion ` | Maps three joystick axes onto the emulator accelerometer input | +| `joystick map dpad ` | Maps two joystick axes onto the D-pad. | +| `joystick deadzone touchpad ` | Sets the deadzone for the touchpad axes. Valid range for number is 0-32767, inclusive. | +| touchpad [on\|off] | Toggles the emulator's virtual touchpad on or off | +| leds [on\|off] | Toggles the emulator's virtual LEDs on or off | ## Troubleshooting diff --git a/emulator/src/emu_utils.c b/emulator/src/emu_utils.c index ffc5e97a7..58283611e 100644 --- a/emulator/src/emu_utils.c +++ b/emulator/src/emu_utils.c @@ -218,3 +218,46 @@ const char* getTimestampFilename(char* dst, size_t n, const char* prefix, const return dst; } + +static const char* buttonNames[] = { + "Up", "Down", "Left", "Right", "A", "B", "Start", "Select", +}; + +static const char* altButtonNames[] = { + "North", "South", "West", "East", "A", "B", "Pause", "Menu", +}; + +/** + * @brief Parse a string containing a button name and return its bit value + * + * Case-insensitive. Valid values are: + * Up, West, Down, South, Left, West, Right, East, A, B, Start, Pause, Select, and Menu. + * + * @param buttonName The string + * @return buttonBit_t The value of the parsed button, or 0 if it did not match + */ +buttonBit_t parseButtonName(const char* buttonName) +{ + for (int i = 0; i < 8; i++) + { + if (!strcasecmp(buttonNames[i], buttonName) || !strcasecmp(altButtonNames[i], buttonName)) + { + return (buttonBit_t)(1 << i); + } + } + + return (buttonBit_t)0; +} + +const char* getButtonName(buttonBit_t btn) +{ + for (int i = 0; i < 8; i++) + { + if (btn & (1 << i)) + { + return buttonNames[i]; + } + } + + return NULL; +} diff --git a/emulator/src/emu_utils.h b/emulator/src/emu_utils.h index 8cdd401e1..b9f35119c 100644 --- a/emulator/src/emu_utils.h +++ b/emulator/src/emu_utils.h @@ -2,6 +2,7 @@ #include #include +#include #if defined(WINDOWS) || defined(__WINDOWS__) || defined(_WINDOWS) || defined(WIN32) || defined(WIN64) \ || defined(_WIN32) || defined(_WIN64) || defined(__WIN32__) || defined(__CYGWIN__) || defined(__MINGW32__) \ @@ -17,3 +18,5 @@ int makeDir(const char* path); bool makeDirs(const char* path); void expandPath(char* buffer, size_t length, const char* path); const char* getTimestampFilename(char* dst, size_t n, const char* prefix, const char* ext); +buttonBit_t parseButtonName(const char* buttonName); +const char* getButtonName(buttonBit_t btn); diff --git a/emulator/src/extensions/emu_args.c b/emulator/src/extensions/emu_args.c index c2c20ff8b..71c18ec7a 100644 --- a/emulator/src/extensions/emu_args.c +++ b/emulator/src/extensions/emu_args.c @@ -119,6 +119,8 @@ emuArgs_t emulatorArgs = { .showFps = false, .vsync = true, + + .joystick = NULL, }; static const char mainDoc[] = "Emulates a swadge"; @@ -136,6 +138,8 @@ static const char argFuzzTime[] = "fuzz-time"; static const char argFuzzMotion[] = "fuzz-motion"; static const char argHeadless[] = "headless"; static const char argHideLeds[] = "hide-leds"; +static const char argJoystick[] = "joystick"; +static const char argJsPreset[] = "preset"; static const char argKeymap[] = "keymap"; static const char argLock[] = "lock"; static const char argMidiFile[] = "midi-file"; @@ -167,6 +171,8 @@ static const struct option options[] = { argFuzzMotion, optional_argument, (int*)&emulatorArgs.fuzzMotion, true }, { argHeadless, no_argument, (int*)&emulatorArgs.headless, true }, { argHideLeds, no_argument, (int*)&emulatorArgs.hideLeds, true }, + { argJoystick, required_argument, (int*)&emulatorArgs.joystick, 'j' }, + { argJsPreset, required_argument, (int*)&emulatorArgs.jsPreset, 0 }, { argKeymap, required_argument, NULL, 'k' }, { argLock, no_argument, (int*)&emulatorArgs.lock, true }, { argMidiFile, required_argument, NULL, 0 }, @@ -198,6 +204,8 @@ static const optDoc_t argDocs[] = { 0, argFuzzTime, "y|n", "Set whether frame durations are fuzzed" }, { 0, argFuzzMotion, "y|n", "Set whether motion inputs are fuzzed" }, { 0, argHeadless, NULL, "Runs the emulator without a window." }, + {'j', argJoystick, "JOYDEV", "Sets the joystick device to use." }, + { 0, argJsPreset, "PRESET", "Sets the joystick config preset to use. PRESET can be swadge or switch"}, { 0, argHideLeds, NULL, "Don't draw simulated LEDs next to the display" }, {'k', argKeymap, "LAYOUT", "Use an alternative keymap. LAYOUT can be azerty, colemak, or dvorak"}, {'l', argLock, NULL, "Lock the emulator in the start mode" }, @@ -406,6 +414,30 @@ static bool handleArgument(const char* optName, const char* arg, int optVal) emulatorArgs.vsync = parseBoolArg(arg, true); } } + else if (argJoystick == optName) + { + if (arg) + { + emulatorArgs.joystick = arg; + } + else + { + printf("ERR: Joystick value is required\n"); + return false; + } + } + else if (argJsPreset == optName) + { + if (arg) + { + emulatorArgs.jsPreset = arg; + } + else + { + printf("ERR: Joystick preset name is required\n"); + return false; + } + } // It's OK if an arg is unhandled, as it may just be a flag set automatically return true; diff --git a/emulator/src/extensions/emu_args.h b/emulator/src/extensions/emu_args.h index 85bd62f66..ccdcbc45e 100644 --- a/emulator/src/extensions/emu_args.h +++ b/emulator/src/extensions/emu_args.h @@ -77,6 +77,12 @@ typedef struct // MIDI const char* midiFile; + + // Joystick + const char* joystick; + + // Joystick config preset name + const char* jsPreset; } emuArgs_t; //============================================================================== diff --git a/emulator/src/extensions/emu_ext.c b/emulator/src/extensions/emu_ext.c index f122da889..9e8d2879e 100644 --- a/emulator/src/extensions/emu_ext.c +++ b/emulator/src/extensions/emu_ext.c @@ -32,8 +32,8 @@ //============================================================================== static const emuExtension_t* registeredExtensions[] = { - &touchEmuCallback, &ledEmuExtension, &fuzzerEmuExtension, &toolsEmuExtension, &keymapEmuCallback, - &modesEmuExtension, &replayEmuExtension, &midiEmuExtension, &gamepadEmuExtension, + &touchEmuCallback, &ledEmuExtension, &fuzzerEmuExtension, &toolsEmuExtension, &keymapEmuCallback, + &modesEmuExtension, &gamepadEmuExtension, &replayEmuExtension, &midiEmuExtension, }; //============================================================================== @@ -319,6 +319,14 @@ bool disableExtension(const char* name) free(paneInfo); } + if (extInfo->initialized) + { + if (extInfo->extension->fnDeinitCb) + { + extInfo->extension->fnDeinitCb(); + } + } + // If the extension had panes, we will need to recalculate extManager.paneMinsCalculated = false; diff --git a/emulator/src/extensions/gamepad/ext_gamepad.c b/emulator/src/extensions/gamepad/ext_gamepad.c index 8654cd74b..b692de441 100644 --- a/emulator/src/extensions/gamepad/ext_gamepad.c +++ b/emulator/src/extensions/gamepad/ext_gamepad.c @@ -14,6 +14,7 @@ #include #include #include +#include #if defined(EMU_WINDOWS) #include @@ -25,19 +26,90 @@ #elif defined(EMU_MACOS) #endif -// Defines the mapping of the default Swadge Gamepad mode -#define TOUCH_AXIS_X 0 -#define TOUCH_AXIS_Y 1 +typedef struct +{ + const char* name; + struct + { + int xAxis; + int yAxis; + int deadzone; + } touchpad; -#define ACCEL_AXIS_X 5 -#define ACCEL_AXIS_Y 3 -#define ACCEL_AXIS_Z 4 + struct + { + int xAxis; + int yAxis; + int zAxis; + } accel; -#define DPAD_AXIS_X 6 -#define DPAD_AXIS_Y 7 + struct + { + int xAxis; + int yAxis; + } dpad; + + buttonBit_t buttonMap[32]; +} emuJoystickConf_t; + +const emuJoystickConf_t joyPresetSwadge = { + .name = "Swadge", + .touchpad = { + .xAxis = 0, + .yAxis = 1, + .deadzone = 0, + }, + + .accel = { + .xAxis = 5, + .yAxis = 3, + .zAxis = 4, + }, + + .dpad = { + .xAxis = 6, + .yAxis = 7, + }, + + .buttonMap = { + PB_A, // 0 + PB_B, // 1 + 0, 0, 0, 0, 0, 0, 0, 0, + PB_SELECT, // 10 + PB_START, // 11 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }, +}; -const buttonBit_t joystickButtonMap[] = { - PB_A, PB_B, 0, 0, 0, 0, 0, 0, 0, 0, PB_SELECT, PB_START, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +const emuJoystickConf_t joyPresetSwitch = { + .name = "Switch", + .touchpad = { + .xAxis = 2, + .yAxis = 3, + .deadzone = 1024, + }, + + .accel = { + .xAxis = -1, + .yAxis = -1, + .zAxis = -1, + }, + + .dpad = { + .xAxis = 4, + .yAxis = 5, + }, + + .buttonMap = { + PB_B, // 0 (B) + PB_A, // 1 (A) + PB_B, // 2 (X) + PB_A, // 3 (Y) + 0, 0, 0, 0, 0, + PB_SELECT, // 9 (-) + PB_START, // 10 (+) + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }, }; #ifdef EMU_WINDOWS @@ -79,6 +151,7 @@ typedef struct void* data; int16_t* axisData; uint8_t* buttonData; + bool connected; } emuJoystick_t; bool gamepadInitCb(emuArgs_t* args); @@ -86,8 +159,9 @@ void gamepadDeinitCb(void); void gamepadPreFrameCb(uint64_t frame); bool gamepadReadEvent(emuJoystick_t* joystick, emuJoystickEvent_t* event); -bool gamepadConnect(emuJoystick_t* joystick); +bool gamepadConnect(emuJoystick_t* joystick, const char* name); void gamepadDisconnect(emuJoystick_t* joystick); +int16_t getAxisData(int axis); emuExtension_t gamepadEmuExtension = { .name = "gamepad", @@ -96,15 +170,38 @@ emuExtension_t gamepadEmuExtension = { .fnPreFrameCb = gamepadPreFrameCb, }; -static emuJoystick_t joystickExt = {0}; +static emuJoystick_t joystickExt = {0}; +static emuJoystickConf_t joystickConfig = {0}; -bool gamepadConnect(emuJoystick_t* joystick) +bool gamepadConnect(emuJoystick_t* joystick, const char* name) { #if defined(EMU_WINDOWS) + int winDevNum = -1; + if (name != NULL) + { + char* endptr = NULL; + errno = 0; + int parsedVal = strtol(name, &endptr, 10); + + if (errno != 0 || endptr == name || parsedVal > 15) + { + printf("ERR: Invalid joystick device number '%s'\n", name); + return false; + } + + winDevNum = parsedVal; + } + UINT numDevices = joyGetNumDevs(); JOYCAPS caps; for (int i = 0; i < numDevices; i++) { + if (winDevNum != -1 && winDevNum != i) + { + // If we're trying to open a specific device, skip the other ones + continue; + } + MMRESULT result = joyGetDevCaps(i, &caps, sizeof(caps)); if (JOYERR_NOERROR == result) @@ -143,22 +240,29 @@ bool gamepadConnect(emuJoystick_t* joystick) joystick->axisData = NULL; } - joystick->data = data; + joystick->data = data; + joystick->connected = true; return true; } } + else if (winDevNum == i) + { + // Targeting a specific device and it didn't open, give up + printf("ERR: Failed to open joystick device %d\n", winDevNum); + return false; + } } return false; #elif defined(EMU_LINUX) - const char* device = "/dev/input/js0"; + const char* device = (name != NULL) ? name : "/dev/input/js0"; intptr_t jsFd; jsFd = open(device, O_RDONLY | O_NONBLOCK); if (jsFd == -1) { - ESP_LOGE("Gamepad", "Failed to open joystick device /dev/input/js0"); + ESP_LOGE("Gamepad", "Failed to open joystick device %s", device); return false; } @@ -178,7 +282,8 @@ bool gamepadConnect(emuJoystick_t* joystick) uint8_t axes; if (ioctl(jsFd, JSIOCGAXES, &axes) == -1) { - joystick->numAxes = 0; + joystick->numAxes = 0; + joystick->axisData = NULL; } else { @@ -186,6 +291,13 @@ bool gamepadConnect(emuJoystick_t* joystick) joystick->axisData = calloc(axes, sizeof(int16_t)); } + if (joystick->numAxes == 0 && joystick->numButtons == 0) + { + return false; + } + + joystick->connected = true; + return true; #else return false; @@ -218,6 +330,8 @@ void gamepadDisconnect(emuJoystick_t* joystick) joystick->buttonData = NULL; joystick->numButtons = 0; } + + joystick->connected = false; } static void applyEvent(emuJoystick_t* joystick, const emuJoystickEvent_t* event) @@ -249,7 +363,7 @@ static void applyEvent(emuJoystick_t* joystick, const emuJoystickEvent_t* event) bool gamepadReadEvent(emuJoystick_t* joystick, emuJoystickEvent_t* event) { - if (joystick && joystick->data) + if (joystick && joystick->connected) { #if defined(EMU_WINDOWS) emuWinJoyData_t* winData = (emuWinJoyData_t*)joystick->data; @@ -287,6 +401,10 @@ bool gamepadReadEvent(emuJoystick_t* joystick, emuJoystickEvent_t* event) winData->pendingState = false; } } + else + { + gamepadDisconnect(joystick); + } } if (winData->pendingState) @@ -469,19 +587,8 @@ bool gamepadReadEvent(emuJoystick_t* joystick, emuJoystickEvent_t* event) } ESP_LOGE("GamepadExt", "Failed to read, disconnecting gamepad"); - if (joystick->axisData) - { - free(joystick->axisData); - joystick->axisData = NULL; - } - if (joystick->buttonData) - { - free(joystick->buttonData); - joystick->buttonData = NULL; - } + gamepadDisconnect(joystick); - close((intptr_t)joystick->data); - joystick->data = NULL; return false; } else @@ -495,9 +602,225 @@ bool gamepadReadEvent(emuJoystick_t* joystick, emuJoystickEvent_t* event) return false; } +int16_t getAxisData(int axis) +{ + if (axis >= 0 && axis < joystickExt.numAxes) + { + return joystickExt.axisData[axis]; + } + + return 0; +} + +bool emuGamepadConnected(void) +{ + return joystickExt.connected; +} + +/** + * @brief Applies a preset joystick configuration + * + * Valid preset options are: + * - "Swadge" + * - "Switch" + * + * @param presetName The name of the joystick preset to use + * @return true + * @return false + */ +bool emuSetGamepadPreset(const char* presetName) +{ + if (!strcasecmp(presetName, joyPresetSwadge.name)) + { + memcpy(&joystickConfig, &joyPresetSwadge, sizeof(emuJoystickConf_t)); + } + else if (!strcasecmp(presetName, joyPresetSwitch.name)) + { + memcpy(&joystickConfig, &joyPresetSwitch, sizeof(emuJoystickConf_t)); + return true; + } + + return false; +} + +/** + * @brief Sets the deadzone of the touchpad axes, with 0 being no deadzone and 32767 being all deadzone + */ +void emuSetTouchpadDeadzone(int deadzone) +{ + if (deadzone >= 0 && deadzone < 32768) + { + joystickConfig.touchpad.deadzone = deadzone; + } +} + +/** + * @brief Maps a joystick button to an emulator button + * + * Using a value of 0 for button will un-map the joystick button + * + * @param buttonIdx The joystick button number to map + * @param button The emulator button value to set + */ +void emuSetGamepadButtonMapping(uint8_t buttonIdx, buttonBit_t button) +{ + if (buttonIdx < 32) + { + joystickConfig.buttonMap[buttonIdx] = button; + } +} + +/** + * @brief Maps two joystick axes onto the emulator touchpad + * + * A negative axis value means no axis will be mapped onto that value + * + * @param xAxis The axis to map to the horizontal touchpad value + * @param yAxis The axis to map to the vertical touchpad value + */ +void emuSetTouchpadAxisMapping(int xAxis, int yAxis) +{ + printf("Mapping touchpad to axes %d and %d\n", xAxis, yAxis); + joystickConfig.touchpad.xAxis = xAxis; + joystickConfig.touchpad.yAxis = yAxis; +} + +/** + * @brief Maps three joystick axes onto the emulator accelerometer data + * + * @param xAxis The axis to map onto the accelerometer X axis + * @param yAxis The axis to map onto the accelerometer Y axis + * @param zAxis The axis to map onto the accelerometer Z axis + */ +void emuSetAccelAxisMapping(int xAxis, int yAxis, int zAxis) +{ + printf("Mapping motion to axes %d, %d, and %d\n", xAxis, yAxis, zAxis); + joystickConfig.accel.xAxis = xAxis; + joystickConfig.accel.yAxis = yAxis; + joystickConfig.accel.zAxis = zAxis; +} + +/** + * @brief Maps two joystick axes onto the emulator D-pad + * + * @param xAxis The axis to map onto the horizontal D-pad value + * @param yAxis The axis to map onto the vertical D-pad value + */ +void emuSetDpadAxisMapping(int xAxis, int yAxis) +{ + printf("Mapping D-pad to axes %d and %d\n", xAxis, yAxis); + joystickConfig.dpad.xAxis = xAxis; + joystickConfig.dpad.yAxis = yAxis; +} + +/** + * Returns the current deadzone used for the touchpad axes. + * + * @return A number from 0 to 32767, with 0 being no deadzone. + */ +int emuGetTouchpadDeadzone(void) +{ + return joystickConfig.touchpad.deadzone; +} + +/** + * @brief Writes the current joystick button mapping to the provided array + * + * The corresponding `buttonBit_t` mapping, or 0 for unmapped buttons, will be written + * to the provided array at the index corresponding to the mapped joystick button. + * + * @param[out] buttons A pointer to an array of buttonBit_t with a length of at least 32 + */ +void emuGetGamepadButtonMapping(buttonBit_t buttons[32]) +{ + if (buttons) + { + for (int i = 0; i < 32; i++) + { + buttons[i] = joystickConfig.buttonMap[i]; + } + } +} + +/** + * @brief Writes the current joystick axes mapped to the emulator touchpad + * + * Values that are not mapped to any axis will be set to -1 + * + * @param[out] xAxis A pointer to be set with the joystick axis mapped to the horizontal touchpad + * @param[out] yAxis A pointer to be set with the joystick axis mapped to the vertical touchpad + */ +void emuGetTouchpadAxisMapping(int* xAxis, int* yAxis) +{ + if (xAxis) + { + *xAxis = joystickConfig.touchpad.xAxis >= 0 ? joystickConfig.touchpad.xAxis : -1; + } + + if (yAxis) + { + *yAxis = joystickConfig.touchpad.yAxis >= 0 ? joystickConfig.touchpad.yAxis : -1; + } +} + +/** + * @brief Writes the current joystick axes mapped to the emulator accelerometer + * + * Values that are not mapped to any axis will be set to -1 + * + * @param[out] xAxis A pointer to be set with the joystick axis mapped to the accelerometer X axis + * @param[out] yAxis A pointer to be set with the joystick axis mapped to the accelerometer Y axis + * @param[out] zAxis A pointer to be set with the joystick axis mapped to the accelerometer Z axis + */ +void emuGetAccelAxisMapping(int* xAxis, int* yAxis, int* zAxis) +{ + if (xAxis) + { + *xAxis = joystickConfig.accel.xAxis >= 0 ? joystickConfig.accel.xAxis : -1; + } + + if (yAxis) + { + *yAxis = joystickConfig.accel.yAxis >= 0 ? joystickConfig.accel.yAxis : -1; + } + + if (zAxis) + { + *zAxis = joystickConfig.accel.zAxis >= 0 ? joystickConfig.accel.zAxis : -1; + } +} + +/** + * @brief Writes the current joystick axes mapped to the emulator D-pad + * + * @param[out] xAxis A pointer to be set with the joystick axis mapped to the horizontal D-pad value + * @param[out] yAxis A pointer to be set with the joystick axis mapped to the vertical D-pad value + */ +void emuGetDpadAxisMapping(int* xAxis, int* yAxis) +{ + if (xAxis) + { + *xAxis = joystickConfig.dpad.xAxis >= 0 ? joystickConfig.dpad.xAxis : -1; + } + + if (yAxis) + { + *yAxis = joystickConfig.dpad.yAxis >= 0 ? joystickConfig.dpad.yAxis : -1; + } +} + bool gamepadInitCb(emuArgs_t* args) { - if (gamepadConnect(&joystickExt)) + memcpy(&joystickConfig, &joyPresetSwadge, sizeof(emuJoystickConf_t)); + if (args->jsPreset) + { + if (!emuSetGamepadPreset(args->jsPreset)) + { + ESP_LOGW("GamepadExt", "Unknown joystick preset '%s'", args->jsPreset); + } + } + + if (gamepadConnect(&joystickExt, args->joystick)) { ESP_LOGI("GamepadExt", "Connected to joystick with %d axes and %d buttons\n", joystickExt.numAxes, joystickExt.numButtons); @@ -521,108 +844,98 @@ void gamepadPreFrameCb(uint64_t frame) { case BUTTON: { - if (event.button < 32 && joystickButtonMap[event.button]) + if (event.button < 32 && joystickConfig.buttonMap[event.button]) { - emulatorInjectButton(joystickButtonMap[event.button], event.value != 0); + emulatorInjectButton(joystickConfig.buttonMap[event.button], event.value != 0); } break; } case AXIS: { - switch (event.axis) + if (event.axis == joystickConfig.touchpad.xAxis || event.axis == joystickConfig.touchpad.yAxis) { - case TOUCH_AXIS_X: - case TOUCH_AXIS_Y: + // Joystick data comes in as a value from (-2**15) to (2**15-1) + // We need to take that down to (-128) to (127) (divide by 8) + // Then need to take the atan2 of it + double x = (double)getAxisData(joystickConfig.touchpad.xAxis); + double y = (double)getAxisData(joystickConfig.touchpad.yAxis); + + // printf("I|GamepadExt| %03.1f, %03.1f --> ", x, y); + + if ((x >= -joystickConfig.touchpad.deadzone && x <= joystickConfig.touchpad.deadzone) + && (y >= -joystickConfig.touchpad.deadzone && y <= joystickConfig.touchpad.deadzone)) + { + emulatorSetTouchJoystick(0, 0, 0); + // printf("0, 0\n"); + } + else { - // Joystick data comes in as a value from (-2**15) to (2**15-1) - // We need to take that down to (-128) to (127) (divide by 8) - // Then need to take the atan2 of it - double x = (double)joystickExt.axisData[TOUCH_AXIS_X]; - double y = (double)joystickExt.axisData[TOUCH_AXIS_Y]; + const double maxRadius = 1024; - if (x == 0 && y == 0) - { - emulatorSetTouchJoystick(0, 0, 0); - } - else - { - const double maxRadius = 1024; + double angle = atan2(-y, x); + double angleDegrees = (angle * 180) / M_PI; + int radius = CLAMP(sqrt(x * x + y * y) * maxRadius / 32768, 0, maxRadius); - double angle = atan2(-y, x); - double angleDegrees = (angle * 180) / M_PI; - int radius = CLAMP(sqrt(x * x + y * y) * maxRadius / 32768, 0, maxRadius); + // printf("r=%d, phi=%03.2f\n", radius, angleDegrees); - int angleDegreesRounded = (int)(angleDegrees + 360) % 360; - emulatorSetTouchJoystick(CLAMP(angleDegreesRounded, 0, 359), radius, 1024); - } - break; + int angleDegreesRounded = (int)(angleDegrees + 360) % 360; + emulatorSetTouchJoystick(CLAMP(angleDegreesRounded, 0, 359), radius, 1024); } + } + else if (event.axis == joystickConfig.accel.xAxis || event.axis == joystickConfig.accel.yAxis + || event.axis == joystickConfig.accel.zAxis) + { + int16_t accelX = getAxisData(joystickConfig.accel.xAxis) / 128; + int16_t accelY = getAxisData(joystickConfig.accel.yAxis) / 128; + int16_t accelZ = getAxisData(joystickConfig.accel.zAxis) / 128; - case ACCEL_AXIS_X: - case ACCEL_AXIS_Y: - case ACCEL_AXIS_Z: + emulatorSetAccelerometer(accelX, accelY, accelZ); + } + else if (event.axis == joystickConfig.dpad.xAxis || event.axis == joystickConfig.dpad.yAxis) + { + buttonBit_t curState = 0; + if (getAxisData(joystickConfig.dpad.xAxis) < -256) { - int16_t accelX = joystickExt.axisData[ACCEL_AXIS_X] / 128; - int16_t accelY = joystickExt.axisData[ACCEL_AXIS_Y] / 128; - int16_t accelZ = joystickExt.axisData[ACCEL_AXIS_Z] / 128; - - emulatorSetAccelerometer(accelX, accelY, accelZ); - break; + curState |= PB_LEFT; } - - case DPAD_AXIS_X: - case DPAD_AXIS_Y: + else if (getAxisData(joystickConfig.dpad.xAxis) > 256) { - buttonBit_t curState = 0; - if (joystickExt.axisData[DPAD_AXIS_X] < 0) - { - curState |= PB_LEFT; - } - else if (joystickExt.axisData[DPAD_AXIS_X] > 0) - { - curState |= PB_RIGHT; - } - - if (joystickExt.axisData[DPAD_AXIS_Y] < 0) - { - curState |= PB_UP; - } - else if (joystickExt.axisData[DPAD_AXIS_Y] > 0) - { - curState |= PB_DOWN; - } + curState |= PB_RIGHT; + } - buttonBit_t lastState = emulatorGetButtonState(); - buttonBit_t changes = (lastState ^ curState) & ((1 << 8) - 1); + if (getAxisData(joystickConfig.dpad.yAxis) < -256) + { + curState |= PB_UP; + } + else if (getAxisData(joystickConfig.dpad.yAxis) > 256) + { + curState |= PB_DOWN; + } - if (changes & PB_UP) - { - emulatorInjectButton(PB_UP, (curState & PB_UP)); - } - else if (changes & PB_DOWN) - { - emulatorInjectButton(PB_DOWN, (curState & PB_DOWN)); - } + buttonBit_t lastState = emulatorGetButtonState(); + buttonBit_t changes = (lastState ^ curState) & ((1 << 8) - 1); - if (changes & PB_LEFT) - { - emulatorInjectButton(PB_LEFT, (curState & PB_LEFT)); - } - else if (changes & PB_RIGHT) - { - emulatorInjectButton(PB_RIGHT, (curState & PB_RIGHT)); - } - break; + if (changes & PB_UP) + { + emulatorInjectButton(PB_UP, (curState & PB_UP)); + } + else if (changes & PB_DOWN) + { + emulatorInjectButton(PB_DOWN, (curState & PB_DOWN)); } - default: + if (changes & PB_LEFT) + { + emulatorInjectButton(PB_LEFT, (curState & PB_LEFT)); + } + else if (changes & PB_RIGHT) { - break; + emulatorInjectButton(PB_RIGHT, (curState & PB_RIGHT)); } } - break; } + break; } } } diff --git a/emulator/src/extensions/gamepad/ext_gamepad.h b/emulator/src/extensions/gamepad/ext_gamepad.h index a47ef8669..74f611f80 100644 --- a/emulator/src/extensions/gamepad/ext_gamepad.h +++ b/emulator/src/extensions/gamepad/ext_gamepad.h @@ -2,4 +2,22 @@ #include "emu_ext.h" +#include + extern emuExtension_t gamepadEmuExtension; + +bool emuGamepadConnected(void); + +bool emuSetGamepadPreset(const char* presetName); + +void emuSetGamepadButtonMapping(uint8_t buttonIdx, buttonBit_t button); +void emuSetTouchpadAxisMapping(int xAxis, int yAxis); +void emuSetAccelAxisMapping(int xAxis, int yAxis, int zAxis); +void emuSetDpadAxisMapping(int xAxis, int yAxis); +void emuSetTouchpadDeadzone(int deadzone); + +void emuGetGamepadButtonMapping(buttonBit_t buttons[32]); +void emuGetTouchpadAxisMapping(int* xAxis, int* yAxis); +void emuGetAccelAxisMapping(int* xAxis, int* yAxis, int* zAxis); +void emuGetDpadAxisMapping(int* xAxis, int* yAxis); +int emuGetTouchpadDeadzone(void); \ No newline at end of file diff --git a/emulator/src/extensions/replay/ext_replay.c b/emulator/src/extensions/replay/ext_replay.c index eaf119e8d..360158fb6 100644 --- a/emulator/src/extensions/replay/ext_replay.c +++ b/emulator/src/extensions/replay/ext_replay.c @@ -128,10 +128,6 @@ static const char* replayLogTypeStrs[] = { "AccelZ", "Fuzz", "Quit", "Screenshot", "SetMode", "Seed", "Command", }; -static const char* replayButtonNames[] = { - "Up", "Down", "Left", "Right", "A", "B", "Start", "Select", -}; - emuExtension_t replayEmuExtension = { .name = "replay", .fnInitCb = replayInit, @@ -581,21 +577,12 @@ static bool readEntry(replayEntry_t* entry) return false; } - for (uint8_t i = 0; i < 8; i++) + buttonBit_t button = parseButtonName(buffer); + if (button == (buttonBit_t)0) { - buttonBit_t button = (1 << i); - if (!strncmp(replayButtonNames[i], buffer, sizeof(buffer) - 1)) - { - entry->buttonVal = button; - break; - } - - if (i == 7) - { - // Should have broken by now, throw error - printf("ERR: Can't find button matching '%s'\n", buffer); - return false; - } + // No button matched, throw error + printf("ERR: Can't find button matching '%s'\n", buffer); + return false; } break; @@ -726,15 +713,7 @@ static void writeEntry(const replayEntry_t* entry) case BUTTON_PRESS: case BUTTON_RELEASE: { - // Find button index - int i = 0; - while ((1 << i) != entry->buttonVal && i < 7) - { - i++; - } - - // TODO: Check for invalid button index? - snprintf(ptr, BUFSIZE, "%s\n", replayButtonNames[i]); + snprintf(ptr, BUFSIZE, "%s\n", getButtonName(entry->buttonVal)); break; } diff --git a/emulator/src/extensions/tools/emu_console_cmds.c b/emulator/src/extensions/tools/emu_console_cmds.c index acb4fe613..33ce7ab42 100644 --- a/emulator/src/extensions/tools/emu_console_cmds.c +++ b/emulator/src/extensions/tools/emu_console_cmds.c @@ -8,6 +8,7 @@ #include "emu_utils.h" #include "ext_replay.h" #include "ext_fuzzer.h" +#include "ext_gamepad.h" #include "hdw-nvs_emu.h" #include "emu_cnfs.h" @@ -21,12 +22,9 @@ static int fuzzCommandCb(const char** args, int argCount, char* out); static int touchCommandCb(const char** args, int argCount, char* out); static int ledsCommandCb(const char** args, int argCount, char* out); static int injectCommandCb(const char** args, int argCount, char* out); +static int joystickCommandCb(const char** args, int argCount, char* out); static int helpCommandCb(const char** args, int argCount, char* out); -static const char* buttonNames[] = { - "Up", "Down", "Left", "Right", "A", "B", "Start", "Select", -}; - // command, usage, description // just a simple way to document sub-commands, etc. static const char* commandDocs[][3] = { @@ -40,12 +38,22 @@ static const char* commandDocs[][3] = { "begin recording inputs into replay file [filename], or an auto-generated file name if not specified"}, {"fuzz", "fuzz [on|off]", "toggles the fuzzer"}, {"fuzz buttons", "fuzz buttons [on|off]", "toggles fuzzing of button inputs"}, - {"fuzz buttons mask", "fuzz buttons mask [