From 965ed711f72760895dfa1205201a7e639f57e605 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Valor=20Mart=C3=ADnez?= Date: Wed, 17 Mar 2021 12:34:11 +0100 Subject: [PATCH] v1.4 --- media/.DS_Store | Bin 10244 -> 10244 bytes media/gfx/balloon.png | Bin 16689 -> 58721 bytes media/gfx/game_bg.png | Bin 10231 -> 14793 bytes media/gfx/player_body.png | Bin 9188 -> 9353 bytes source/animatedsprite.cpp | 13 + source/animatedsprite.h | 43 +- source/balloon.cpp | 609 +++++++++----- source/balloon.h | 77 +- source/bullet.cpp | 2 +- source/bullet.h | 4 +- source/const.h | 36 +- source/gamedirector.cpp | 1616 ++++++++++++++++++++++++++++++------- source/gamedirector.h | 214 +++-- source/item.cpp | 2 +- source/item.h | 6 +- source/player.cpp | 247 +----- source/player.h | 10 +- source/sprite.cpp | 8 +- source/text.cpp | 9 + source/text.h | 3 + source/utils.cpp | 8 +- source/utils.h | 13 +- 22 files changed, 2081 insertions(+), 839 deletions(-) diff --git a/media/.DS_Store b/media/.DS_Store index ef9c65c8c82754edbfcf861c591b56e13d627467..0df0db1d62d5c50a93d4a063671652462b36e25b 100644 GIT binary patch delta 42 wcmZn(XbIS`RDi?C*i=Ws(A;G5ItjWYpPF^SBy!o#9R(=$b$#=!o0o(%-PXGV_ diff --git a/media/gfx/balloon.png b/media/gfx/balloon.png index 109efde929b0771720497b332648efd5e11fe08f..c05e0be97d6feb3999577a02ae57d717403091e5 100644 GIT binary patch literal 58721 zcmcG$by!qw->5w>z(|Xvl8Vxe0@5g<(%mV|Fm!{IfP#c{3o2bh4GjZGry$)W-7WR4 z(ffX$`+47YAKyNXz4sp+i@DZsT{CNDtt-yod9C+K3NrXO_i#WU5dO;-k}4n&njY|% za0?44xp}vAGwRAiokiuq}?msx&q6JOkIwMhiF(} z7VHFAk3Y?vq>^R*w^TQSaOp24$3cYR>!e_2gIing>S$?!A= zqQEzHG$_CiqJwaY+F zcQIs%mM|MU#=Jv&-1@wOXCli76~;by&qHO@s!aETOFUc6h>7pF#{TF>Rrf~UyN_C- zY0q3MFW9_JIZ>V{PsK-1K%g z{VZ5WjU*@&RKHv{`sxzB9gQo>jRxYmm>nxFUu1;P7Y$>T37Y-9z9W9-a>uemkINGs zR9{}AT-V<_Z5XVZLvJaM1D@qNF%$BaJ~Wbfu=WveFJ=X&HPvUr+;4`<)pYRA(F=yN zF2|tz6A8A_GtZr>vewSuJxcfyR7u|wAvs8&G|$*W8fYY%`_GJ^aoJ2MbPlG?0PI+gApOm&38D{GGyV8217%MYaLqRm(lg( zuH;i@S;u$FdPJ=%UO(0X>5?A3kn$^Lhw_hBWrK`^4+= zo<0x6)QweJ<`W~5reC(SKC86%M3iA1ePU2@u#YLrq6l79+W6#&kBP_3(fbt_3`p_VOeZ?wD+uD1|hI>JNoQO6it8P@cJ#(x%Dbw%q2j^+7pXUp%dyphA1Yd!H;EkVeuI%`k}X(+JQrTP_IR+`qD{nU=I4otN zu@S~3WVOcI`6rzAIbqnk2hZ9{uJcQ-{8#BFMuJEJpprpFEX6EuX2^XteYVL576!}8 z@<34(B5T#%#T>z`ugtzA!XI&}U2c^8nxu6mDAU(}alcbqZ9IQ5e{sxb-)&^<%|2Ca zFkw0l$O3=8A^o$yKAvW5e=sgIVb+&?r_qj|XKo|-lsK5NNi(Q~T?gj%wBl#!FY7aM^VeLpvVT|Awmngi=xqTGl z0Uu#zDrRBbTsJ7uTg|MQQprum0=?iz#JwMUv>w48KyedwlZ*=ueE3`mQ#WI5c%GVj30QAoAjxMoWdO3?PIp!P<&m%biC*NVvDtjFp?6TiWqP) zxA_JWQQ_jQAlF7l9ZXmDIO|EPhGJom7`W0S5&E*KJb zqgmF58sD@J*lzw(-J}BvxNpaJaQA++*~yQY%ZXt&-(dN>#*1-Ia!ucp5{94YUiu%v z$b6|Dm8!m*+7nOF;s75xpl;`h5!KC3%DDS^0UT(=XCGe&g61dY zVBK}*Zr4>k8_TEbU%RDm4V7WKne>B?_dNI9*^|IA(Ii3OH1h(h>NTpf^Q|TykWucA9!n<` z&|Iu61~{ESj-FYKd^YRed!K45#KHKtcjj5-91i&+cbs z_~q~EI386iZ}?v1Ep-cvMnMg)F^w*1_!#n5%- zO;3Bq|8jqdQYW?n%e? zZ1Uog?Tpe8#4#WmIb4V7*$O$TJ4k-NNbHig~?_JR1F-%G^J{|mvg5xE*j-{4%7+R9o zDI2&~n`dveohdA>cy{|WuK7~ITu~sQ-@gCE(iznUk80RA>^;o#{bSnk4o6Z0*qzP* z{Y%0OM*4u!XPYE;@!#hJo_bE_Ze5GqY+Vf1kw(rX7!<2om4vA&jm!w(4sBM-dkhTF ze%Ib>T72KXMK7V@jWO#g;=`I`f-sH^EM)5%#`L6{0RyNzSp3ReS8Bk4)4)n>EQc4)QO0$x@w9)746vg~M3EZ&8x7ksVhg$2|>4 z9qm5rCQVkaiS@&$Y7`_2Uc%e+LB5{1irAE{3e3cqpV?DR4x@H#!o@@4(?^02cRPj1 zAYi1!z-LkiHdSoR5qfh>+D+3-ZWV2In;MjA;Gyyng-MZx&Q+7OloDH)yF#a&I)tznFqCT_bMM!GMo%bpO5*!E z=qa3b5u$4le{1`GE)15$g9ptDWCv~92M{2RbVwBWIo6GEk<6*~)#vuGqSg>R!Um3! zK6ce${~|eK@YH{XJx!fYu%B;Z*n9cXJ+KaB?`}&2B4l&I^Bw}hsh>A9RGFPZ=11_* z7--Qz?s)SccPWT)|9q9_o^{;t42Kzq__=?3>c_(Tl**o{?s7jmGC#t@rYnFwr&qzVXBVx{ zSYc3%onLXKSWZToB?C*1ZqU)ez&dnT)KzhA= z$ znAv0VJDT?vwiVDR2)s|2#-C~@9*k~fQh;W`y0I6a(`Fs4mbs@OC>VUkEsU@)BZVH0 z<)Jy3{U(@fH__)xR7wdI#Y$gk1+ zcJKnS+*b5rID4nz%znN@+QBA^f0S0_rln(rtIy zDez^LKQCsPKx$}=?MHmQWpt{NNyR-uSKbJuN1}*nB18z zTS=T^u={h0-hrz%=ymN!X`|5w!y`IeVfj*faquD)Q(L`O97#r;6n~A{DmEya(h9?T zzTSC9_O04T&5q_f9{>RkRnt$dEC~5M*R*u*2JLj70d58$UnpY4TnEQFWSZm)L2EFs7=t%3yL=Cg_Kxhb`K%B3B5Ze#BIG0w*q+m9_@{virL`n>TgP? zFsx-WtU4UAzhlv-5Ra8LG#I&vb!V->O}^SL|-le%wIz@pN* zmDFPA?d7CvAlq(crpoJ8tWvDXp*-sHfR~v~OTCO;WuE=;jNEOs>b*ykSIUW;Qd~#= zt62H8&P64P@%&H{pmUi;xW7tCJoPd;%gC#B>3-C0Jlf?mpISGI&P6egy_X;9SL@`L z5Mi#`VvZNmvZu>in&oX=9#mVd z1~W?MkB}KpIN_a%4b_cp$qFo@YI@{H(DXA9i=|WGv74^-x3NgOILgGZCHHUEPbUYQ zRasykAq9Wp3?4(h1GS<`t@?Vh9KlWwRFTys<6k7)fEZ<*1OkNPWjrW(;`9xe)D-&b zYV^lM$P}U*zqidn*rYP`ci=0sL$n{MxOCVCBY3`JMtcbg$jx=;80K~KD2yo&KAsrD zz_Eu5Ux>x|zW57kQA1bG*wK#Ce8TZXd>m9k8~09W$92k zKYTm<7)HB{eP?l#2ii9G^yZz+fPMVKXXXDnuW0>7h%)q-!9 z$1{q=u8uR{RxsJ0bN(ehZV+T`b3ugPt^nmDPAZ7|J<%nBgEK96PUiqKVXk!lssgf9oSopf zZ$>s-P{}K%NBa6!;Zf1lhlmC~g+h3ES&)Nhh;2Pnj`Lg3&8t9!@)bKC;>H*m+;)r4 z0hFS9|G0%(*UK)&p1$Z8@MSIf1X`tg`gtKOPo34k{^3Xc0n z1gCaP9;O;tpB;kUU3@Qb{Gi?M{qBy&J6wdLK<1K#7@G)iZ?FJkBBwNW}6^HuUHYj6oo8hSQw5EQ7^jQ4pJ8Yo%^st$saGo3MO`Fl)_DiGZxT4R{oAv5fpv#ggv=j5T`!)T*p4*h}>d!O+z8)o$w?$M6Yx^o_ zAkkH`=rJ9}Ek<9VmqU@M=NUaN?AcI93tKF8Eb179)Pa3q3mCTW)_YzRe9*U<0?g6i z5+!yvdDp|4L(?}AsKHG??f0aIgc`-Kh69A(2B5+*u+K=z8HU-nCn_q|H67@h^20;f z>I<5ZL2opb;0PKBNB8)5m(1mfHC~Y78%F z3ibYuo^HWDpTGvLiL!=r+&ay|j;})Ie^q?FD^#KY^1{s@a;77^vr6UNs$3!ZgrLdO z?Y02;&Do_a=H6wUR6*H-KV>$U&@<6z4(7hs++5U(9iXziWuw-Rj!UkTC;@za|N5q| z{2|HRRHL5pn!@>=syw)OtSy^HtgTt1O%4}PKHs{E1a+nXP@`kDS!O3Oy|iJahFxgGihJnedZXlv?E^lHf` zRC%3UOCDe4n5izT*|MxaKA$k4I4pz`D|Yc|qUS6y->{^=Rm(vGqfiQ6HQMl?#6moz zT&s1jHWtsrAdN#UUN#2N2GDR%087^M+A;8a)%+A zdkWO)g|`$Jx|$*Asx}@So{?gioygVz1E?VIQF9}xNfD;>YyTyXO5WNTgzH7$M~XPG zIk?a_#kO@?dS^fQAO5}KjbVXM==C2MxeTxrW9tb3JV?RZvSJ|ug z&%KQJ_DGkIIXr%*?>8&_ODG^aikl=N#FS^D4)dz#fy}acIr!df0`|W8f3{6J78rZ! zTj}_j;RTJ?-5ZK~-`C}|bMg#X^Td3ZTzR-mA^vqa4P*aX$ zt}lFiZ%wysiY`?kt%}pTw!`AC}OtGRDjx)n!1JM!|h!gVzq)BGheb%#~0)8yT#k>4Z@XG3{b563Vci zk~VDp5faQ&vj;}OpV%}hLc`lu#!!v^!o;e^a`9>(|IEj_bmOhgjJS)T(UsuW(}uy5!0`ar*m8lRk`V>Al&0y++0Y-@8wUU|IVZ3eu+>4~>fZR5wm!To6E$ zM=^+xtE(x*^B;KsH?{wb_bJH`##`^;_#1IXth#H#ezrKzHrsiLYA4?lVj3>J4qzV? zI%%(n94dFMu&_Y^cTz;iPnhiYNMRbr&c*@}YbDrgnd{#iezue!fg<;n^l!o=ai;o8 zwqE=l)_{6}*0AIfu=TD1Z!v&93SVApQnQ#Bgs8FOs(pVur#zOwFP2+1jmk*Y@Fqu* zt)!=A&;gtwCo19Bo5c_dy)~Zv&;pE-37~ggj}?-jSA4UYn>pWa2V18$xovLbK%fl z%ZCVus(4i0BZ3xxj%&5i)3k8}f0q$&_HyEE^0VdO6OSr1ma{4IhG`@Cr_f~A z@8jsZmm>|nkBjLEs}MoAaf^h`dt_b*bAa8TD~P;^^4>``4};RHotmJrnDFBN;g@z~kj+Y_5kI|XRH z%Y$+0AXrqohlA|was5iSbt{O-Dnr*bhkh>97E87lj5?qQ0Do?mz#st;!Q z_{@5}-nG>AybTgA&!*p{-4+=mCaDcowMkJ!?xfslKT&B$%R zb#McS0KJ6S{9cHpom%i$oLUUIxz!q5TIy@d+n^s}SJJajE{5jjG#Hx{bE@lKkwUFf z&h)$$Z#yeKu8KRacWbB{h2=>7l_;(a!;**Hm4;Xii=#PFHC>n?e#EXgwHE@ zUqKYLX=T7O=>v?KL630FQ>=b)GMu(EyDS8TyXh_~FCU1%P+pB(lj}b0v%HwdV%w!X zY8wt_@DpQ+e7!W2Y*NKCFv?f_O7Qm>ZYceS!bgBgy^pV|UFky&_oW6=qdOe+;XBoO zKDQ`$5#wJv>n4RN$I3m{_LrmR&1#+$zj0O%MGy)UJhs~geuqJ_&2jgt?D3-R^2+dn zXxHMo_>DCMM#i~pnoME7@MYRZYv|g0cbwgT9rteHdMY<7_F?7x-4th4Ah_hHjWLxU zVX)S&YLc)avAgQp-G?3wU0?uJcg1AUK83M%=sPif?qa&#vGoq1#vgCcvR4@eJ)+;R za8f33-Ay6TvC!ilhDY<_46$#&%&7YKlzGWKwky@FFB6pE7B+zg9*2WV9-CiQ^|#Yr{yIPU>d?i1qx7jR-JxBs^;JL9 z8gMdbMq~N-9vANiI-Ve=6Cq69DGan^?9`GS?gI9$(|Vgbgl?2 z!><(?(&IzGMK~5N;BSU&E<~;Pu_g*5DwdbdGkw_9K;_jh5s2;>9!^@e{^R%_u~I4l zbJDDY0A_PaX&&d7JkDq{sg?Hh^A|ci&bSZ7HKDjkvuEgsqGGa}64G)1jxxIZfjwSc zd4IRG=|ylc8&PeHUCMj?0zRVyTi(*wX6z~6Q@vk>iN@9I%?13(xTwI8n$OsHX@_K& zyP8sh2qy63woPsP>OSrAkJyDrI)mYbjcWJpNf$6wv;BT^?J`^MHzPk<16J1faG>4bo?Rw|WH?Rt?_5JMv@MP@N zRbuVkrLgeZ1=D~Po%Hx?sqr3xc7r2_^jbtt>yV&nt^)zCn~3_?W_Aho)8&>@Y?~Ff z-UL^28egCd;~A9N#zz~oVr5*~IC7dOW*rv@`)E?FM*UNeMgNJB3RUKgiabMiR#~r< z8a0^!%M4e&3e4Mp=Ndn|fPucV`@tF+0KrF$0SI1lwlGj}ktD3`;MLPGhe17nNS2G0 zHA@Q175o5<895D(ie1G>^ zAUKXb2pj9-TtJ4&&%e0?wI|o|fC!Ibw(}TWbML+E@S}cSe%cK>11a%hBzj{QX^%UC z-bg`|X0boHv=rC|eHtap(CH!Jd&inONE=74GOjh3{LF`y=~O)jHUHnf-vSQHa^_Ii zKwIfGZxFL=&`T-Z%09PhEjiCv*sDEc_)0m|4Wi@rR9Y(;`~0nfE1KEqeqFx|K!N+T zTxTvHzsmbOt(le_LB*ka?&vxxoha9o(0)s!yrn=1RfHGY$PD?jAgMdU2}NwwGLCp5eeu^QZ2 zBU%tr7lOWqULuU%UT$ zF^0$oH{93wFc?tah{W$U9C^tBjmpfANmK`cc-+x=oxk7}^h%Uk>%gus43N}~ES>oV zWBy|pGw@cHzynPeo%73s*jxH{^9A2)V}j@c$+@2MGr8IXuuC{x?#-&PpmWX>7?PT* zmuw>w$P03 zG7XI60j%QlO3}`o=t14r4Jun30u=}DVY7}^vQvmxVDD|_`*GxQRi3h|Nz z7Bc!=Q*`#1n0iQ2cm%fUlZnq^WdtrrMPI=2{Emvlc5_bk+YUSfCk z3{1}kwNeU+N5qydS>}g4(EZk>bx`+p1}u~voN!?H@}Q4N*>S;N%ic&Qte?zpTApL% zchqE@SNX)P+Ed6(>Vp}#R=M8uSp_5Wrvd|@7XjR~_4%YsvfvKkJ!%VZ<@UsbTm7JT z@+&)4ygrm=2vu21DpiuhE4B>@k!>6L1QVtxdVZ4SxgFB-U-$}s9D+EC=V&avw~E) zX7zXLnPjioe9D!iWS~eoC`G8ABQREl+~hYNbGSy!Ott z_r4SKi*ON88Hi%le;cD|q6=a*!e1PxnGv-pE5xCd19E3D1?f6(>17wqd@#G^xRKalt0;n;SeoYSpY}j zDug+&24!ENsh?nL(F$^(e`=vdI@DV%TD$wzUT zW%9=?7hIk6U)&ks#zoJGRRbplPhjP1HM)|2F=_tBh8p?a3z7Prdh8^TPvzE|6xqZ{ zVJ%1v^{+_XpZlCUg?7Y;Zw!5H4u51IA{G%b>&(ERd+{Fiw)?Ss+`CoF0g7^P+f54d zv}s6x%_ci25G@^4^6OTv5X60*$s08LVPVfLEnH5Wm7)MNO4|w-n!_|aOA2`Xry6hQ zc7yG+;r$rY#!cdDw=-f|`XT}$Q93DPEMCy+GTGib^ ze$c*uYs9g_@bAb_$fgl%haGlCY`*kVh~c3`;Ix+nRR{f!>_urPEpMm~neKA)#-9*! ze9Ad-%)0}kRS#U&gFa2MoEqHj*ncrd2ZB~qz*Jj!>jy;BuCk;-S6?w??QVaxWNvq) zM4sN~*anZRvL$)K$Zay?3U_PeYXSUcy<#RN@GDf&A~t-(VoYD4l5qr=MjYJl|@^L=w+U=`u|weJ5~xXyHs`?i%0Pf%@3D8g!}5OVp1~SMkGqjD*iQ4V`wI7b zsFVE+qtO20c@-oZ#k(1ge`{xyPC54C9!sO`fRW!;f|M@2CR@oCx0@^;nupney}Q z3^m5=o@v)#R*vY0)rdezuH5nr&E41=@KOX)Df;ol@XR zpBDTCOnIz#yYWAmbW_i)n_j>r6gGSBWc&*t3(fOJWUsv53)@v}IowSEnq(iW%Pe!# zYRDc8ZoAsjbjB3iI}1S)?s(7@I-#z5@#}YK?N<=Q=PF7qYS4$GhAMeKm7W*1lyiOGXyu<>^G`^q~HLoC}v*W^Nz- zHo5a}Je(Uf5=6XgfL}X{?=h8?;>OeYY17&!wTNz0X^*cV-! zIiumF2#bf+p}^3*T80IgoKYYY`GN+f+ z1O?q#t%=h}ODy=q7ces10-pa88;#EfhELz}nifTOuQ>VQMwzd3GXD*Q3#%qaW|PJS zH1LQEfr*v%@s|RXPxoF@fkr4#8ew(H^oPiPP!BQ$5Sdt^gB+@ zKq9R|VP6cNx-;Nm13CJBq?uL38z*5wxN=cYbo{e1r@%=~m#N5V`RF{6pcV zhnyetL~!bzNvNg;Q|Xu<+#cR^Lk*OxPEhHXtldh!f-h!_K~b$7&?0QN2Y8n zuJB8QS+&F}H}{kGV`JOqEV+|ki7;K(?)<5^*qtWddv@QjM!57>(&Q02EGbFrqBpog zE6u!4LDjf<=)4MTZx0{4jKRIXZXL(IeP3~)(L_JE;A!NvCo278IqmD0m!Z@9xXDEJ z#In`>`zs15e}D&z5b}6s?~N9z9<-ntUofnZkk$&;90{Wmm^bFFwf{uNB7{Iz(x72s z1t=VE#Ljk+{!;-^`o?nM3@A>o2-L z@RvC~FmKhCVmy>DAY*fJAs8*oogf>QXEvCa@M?Q^WCOxb+Ya6bnV$?<)5uXT6}`nLD% z2UmcCvdE0UGmwe^odZ({lA#-@RJX>bI@<-9G>$->(yt; z-0s={U>DrosAcgN;BC8F_&`}}yi|06Fe&6!SPMooGb}uG{#bd6nW#%Yxjccs4}PdH z@dxHAY`aYU{U2exyp3dvux^HL;jl}4VyXph>g1*{HOVBn97S|R)OAz75&$RgTAICV}{yS*rI}I%$pTfbdG}FOkxHWB?=)fC;X&E zBCktL(dXAeqC-h#{U%R-ijExZ3o4el1jiBEOWguc?a0NctM5M~aMLLotX*H9t_P{xmBoae*<$o3898*t5!;zP*A5eW6YTARwxRUj*6|!B;xA_Mx6-=i zU23h&!P;q;0KWw<&}BkLm>xgWoWQYxzyk{vP+qrHnqz^qJ}+e%X?q0zR>09-CzKqa z6oXAtbgwOVnMK!l8{`@_lK0!-R)vk^+L%8_d@dYDpvz}5@%O0}-6I9QJ>ASv55Z0r z_CipFilGkirD;tIS4nAgAw_0E@@G1P97Zon<$hjTT79XLI;bwM9Q9xE?Ee&&71T9i z%0XSDm(ukP1$sq;VVUT-vUqGAvlmtt}`4Igdz|BM$(ne>vEH zL0WZ*NHx5*;GpfFnyz0(-8|IO!l;Q55)F5tX1FuA;eE$l+oewJ5}H%#rSmr4lH)4F zdqB3D*+W<%!$-#JNBj5Dh_ff69OLK>ukE1Qb>sBo8w}4nqnp}++vEwp?@0|QDmKbT z(+f3*!aRx?!!ARyT)@KK9jDa2;5fO309Y-HkO^ntA5%O2ByJPW&$d|JYMXA0Ps#} zHuJQL@`;nwW%%t~x5Pi}zsI{xZfZ!@9MAA9@nP%eSD;^oZ~0Fc&G-%Y*$G!nZ+Mpt z;?wNq9>AVF7}wG78CJ)nflAHU9X58O!TwU%V^mDoi z9NG8D)<;xOacp@8D;I>gn&|OaO>?WhBtA+PCLF^?-22GS>QyOd=D#M|?G1Er^@Pm; z2(7sOG>nN5d_SADWtCswPp4RAa}#2Az?Y20?bxTES&vRWz9BhVxHYozP+`QSwEWk1 zb&s(_Szq6j<79oKf|xb^?)2>LO}qo=l;lWKy@5)RVLDh%nc&fJMUbt6DCFjZE*1}l zZXF!xI}mP*NZ(0fpw^r7Sfv&<@1@wDV}VTn2RzjUMi$<)UV*#Ew(KL~Rt7RY==QhW zzj($e0}hwONZqf3$x~|7z>C#+>3~HYo@Kk}v@aqtXe;Pg-Fxb>Qz>s_m`4*(R=@cY zz_)g4B|M>OgIPpEhSgGYKQ5EVtG?=G{u9P}r(Zqj`EPc!JeuD?4GWt#*Oc6s&k80x zxJ7T4CA`S=#r;d7v@S7f7Iuo)WxyaJ zG@}k!N8qwwk@;TY9*`?5;z$+eR{JHgCLpb@mnhkVoLx}eCbzi0%XA}Ya63hWR-Kz;X z?dk?m$j+X)4&alyEMSCY4(&>Ga9cs|b*U`ixbl$b4OkdVN8d}4R(TSdBo`B|ml@KJn)dN|%Vf(&m6;dYV-K#-3|@I!m%&C-vAEX@;SZW zAwxPkvKIKAP+GU4u&t#DN~^07f$h4Y^Z{qMdYdbnRAHqBdWnEJxFqd5R;cQ8VgtWZIPXI`BP7Wq$G)d27@Wo>bv3Z^ zFY7C6?w*ulmUFMxxy-L&ZSLFG450m7s3l^AL;Zc&$d%fiVnET3Sds%y$-T0#UN)^7 zQH}v~+5I2YmA^hhz^Civ4J4*(-FGrmw$(=8%i07RAO2!+1x$j6dmW!YePQA@>uI~_ z3oMpEos{RYVl~m85BuHPGRd`wPl9NDBDVMEE}x4~in%;Piwa}^9Zi~Pqax!bm`8J7 z&bY-4*qVLzAa)}GECNRCuxMF_ws7kZWaKXr>o(+4jnJAwH>nbK-}0A=LI=EE_qd$6wV*Dv2DGY&P2!HqVq_;~5ED zcIoe7U4THdg15!V-B*aX(lHA`#}J4%LVyTxA3vP;V(vA>x4e%cj{g2a1t zG1mnIlQIgrrMcQ*%)1)SFXRWqc?zR51d0y=rjH-)q3`UeOK2R|64W!S1>e=Eak@gN z0r$^SG9)cAyfHmncsy+rx5Q$F?dtez{ZvgSZO@lL*mNt4@Bb0C%~H5(E`L-CI4a)5 z0METHH2~T;0M636xO#aXI~7?uq~wny5%meq05iC8&aJqhyg9gGH1kVbtU+Fq%r z)2}rDYVv(xmxiIQ->n(-EtVs+W-^sNEe9X!0Zcx26n&NLMLK}y`*s-qi}w{Bb45Nv z|6;V(yK)3iRYtG+#rhPwY1woY$1rrM2h~uO7W{M8^b(4@DR`MA;n>HfsTLKWBXn(fPj>y(%A#%@ryYG-Iao*Lc$y8CbMkv0&Nr5{y~Y%A@tla@nK<+ESdg&msB5%~DiTU#1v8 zK%I>IpZO(&E0kx8yg2lulSZm3TDr3kkr`Hqmo+!DnoCvm2Y5l_tem1w~@?=Zi2z&-z{@#GAX zSr5(TcE`!u$X`+v@xvOcf!8 zgYvDy?d)IxbtNnjvU_Geuo}A=D;sO;xkHB94}rVefB!;|3{&NGO3>oGZO^7l${w1FxpnYO`W{bP5a*_ui`hDR&P0C4%8Fvs{S zLNd?uFqCd2e8WoghnmLC5oH#NL~bWDFFg3m1+G`9;pY@l@qpv$cSqCNouLNJh{Bmg zXOV>e%Ussw_SI0;F6X`f!e&8b7P3t$JH@9zvurWGwAl!%xKN6jkf@3R%j{%A9-4qZ z;;My<5WaPpJv;fyN7wWtQJm$(wE8!c&DNcaLCMX6C@2lF^8hB#_@NZno>6g-TUtZdpQOcCsJIW%2FhM5s-prrweYw z#eN{*)8F&3q*7sA(SWfM_MuXQzFGb9{R&b<1#J{##`iLTF?N+`JzHDJ9m$>=Md)q) zu+d>AB#T^;GI_i>k)jlplaS?)W9*kQR!=5dzE46zV~@+Y3>GVS_t{zpU0mm4awSOJ zN-!!!`484zvwhgOvN9%%>S49c9qruxScWykYFUo+k`)=b96cq}lL?lM|Ek7JEm$5($LY zj8A;(7MdZDDgwq^uNr^__s5>FG!pZuf}wxWS2M~mpb-ydO%GxDbPB9GBUUW2mi1lF zKKQn=sxxfjk}O}VrbP7ik8aDOCrD^ZV>eQP;aZ=3!ssK){Cvbb4W;hZAY)Abs>Mxj z1@rys*fsMN>SShtfdjin&IEvDNhRKug|~&4vA1R&4zBE@GQjUeZhpP`)sm9KXqG!! zJglY#cfPg-346c(j$`xRacuIxajYlL(BO8U)t6 zU>Gmt#raq1bKH*A7Bq0odssjr-W1pW0?;;YqyTHWD)T&=<$0&vVT0kE%EcLMcW*(;1Y8 zpYD@l=zN8_jbRV}``7O*ty-R@B^1xLMyr(;LAj_X3-aE$0G8w=`9 zlC}Q4yD89%_$F7ru#$cAMzERe-5Yv^q%0%wOa>IZ&;3D@B|UIsT=};v3`4QnwaGqN zQIrIXj5qyX22o_EB>bksf4t;>qF_at@%H;mw5<&Fw;5QQF=GLkOE92eLC^H>*IV2H zro;xfV&dk}UjbTgh($p;5y`(_XI+^@g=g#qY$v9`4e}@q1kyyNs!7fu@Bd=D)je8E z!GH*T(k2LO|MiQrkFOhM=Ds`&fTnCkH&jE>CJ-Ub7l!|M=Ew6LA<7EhAB2R|J*EjK?|aCiY`xaX zeDx6Wu3@DLDT}up1fT06R^C*xcvCnO22lpUdQr)>H15JBV0z zXS08kVn7!D3PVYz|9&b5kUO!ZajZaZ>S4A zenY@Wc9t=`p|p~AG(DX9+{Rn)cii}-Td53uME?G8~Ht3-*wJCJ)-0{jd-y6w_ILZI! z!u~}9dH4S!!&2Y(*H1I9)BzP(&sg@3MR?K)ZW(8oku353KM)xeERp)e+|w}1N7>o? zmx!hBT?7M=e)ls2uKGY)}2z zYQhYw0SZ#YV_CW2bey-pAI33%%Oec{!K9RL!{&*b4r0_6f-;=!u?^Rt-_WKkKIwi$ z_w1N|H??95L`$sB)Oe3s?HXBAC0FvHO9S`q|_Ip*8f4y?_md zBqr4@jn`ic-;zP=b(MLbXkD!?0Y%ae)Xb2-$?$2Qd6V2M!c$%b2LwE@!YKEK9Yuig z)c0$;H!Fap1+jpw3rGN9jTILxkJ`SFWSo-K;Q|%VURF~R`;TL1K>6iSYg=V086F)D zSLJ~Bb*;|N(Y%drmKX1{-`lP639LGH9>CDAxtvR6cUFgEV$9!roh=oQQyb=$@hd9@ z%W_WUwYwfP$t1fCD16HKRLB4Vns-YBMWwalg6PMl&GcUHP}~gG^^x$hBh&`8?cl>lc|U)H{W z)kJ_0_o~0=nXp$uutV@u!YvH4>Gvf!^URw|mKiDNpifTfT4hFvBx#KpFZHNlH1^(7 zy$I&#rzIBn7@$jIpOB#lZ@J$JZ^5~aB8vL%31FoGOU$*CkDP$Vel!G#jV?Z+&O4NG zMseSJ{~v2_9Tw&G{%z0D4N8N6MN6l&q9`pffHVRkDbk$+qJ)wH5`wfe3=IQFDk0t7 z-5t*waPR&7?f3gW$8$XI``38f_qrE@OsxC7KI^>BMIgL8a`Z^og}G=eeG?AEqiLg> zlOA1QG1FAX;9P{-nBJ&Wzb=`PL0gh zQ>7FCVX%&XiM&4Y#H0tM<_IkdkkcJ2OQr*Im#Xr|SK;1PkeX0VxIG2z7|6u(dtBR< z+nZtd*WlA9Gy7JT33VC^?54umAe2}HtHP(JjBZkfU5#`%g|F*~=&HM#Y*%GB-(6X8 zSjaW`_w+&)J*^?@c3}Df^FE9q`8)UdgGVc{swBvYZ(Gc5tz3yP3_BcG5Wn&JXQpft zQGYeq-%-#X@)=TSRxx6~3%xss_by?9HzqnF%%iieKjOg@N*%e0XH4yZpWr9hpG}Sz*4C0CQGdo*NRl6d~Te+P~5K zJDhRP53+C5XhlF{Z`)-Rg;CNGS|3ib-<=NcwC=?cl zFAVB}zd&csr=13OY~)99!YpTOnO@!k5#X08ocVmM@U8oAk6zv7`qBl^;G_!yHMijI zp54{Joof@!H^}dzF4t(8LlV8;U>u4~a&7W9+oz1wJww^FI%I06AGQ5&4h*H}&%)|) zO$%L58fsqtXY-B)Lh_#0-jRR&EN-VB3jmQe??~4E|D5 zoSMNSW^E*mCicCg(f_}ZWZr_m^1hWu5DK()Wj!{3KK;;3ToTE&tyTUhG#FLX_gi=T<9Am~ai|C15?koYa*q|Wz`7Ww?-lfqCdAg1Z^Phz}XzBC)X1a`1%cwiD@ZUxRoxLkPUo?GQ6~ z9@&_AaB2@tEiW@Rl|di{NDBLqelk#m!7`IwIUe=eFm^3Q_kxaEoy%f1_zvb;BIc+C zi`tW8a1TXoSV_lwVw^4c&8VDd8^)nS6}S_GQx-=u=1Qlf0;)J#C^I6x>p; zQn&nupf>A1wcsWcufvFNSQ(`z8%39`q0ldUv;8qE+W> zc8~pvJzY|$I$WB(he&wRFHDw)PU2X(SEt91T)w;)HN#)C)ZQdTz@=N;Z3u@e0S3oK+g5Z z>mkrNnd%N z?%oT-cM`7XLElLE7l>Z3G+>x_d(FsMbmNN&a9K{yd;BrP=V(iUd(W_*5qp{H0dqv)DfgBmZ=6=s2tV0f0Pu=ZE`##H#Cxnk{BR8HGmIO{SIL7pwzf60rJ^( zPNHDdb9Dhb$Q^^5uH+y8SGf5NtV`YFTE3FLEc`w8STdPdhKPm|V7XbkdB|+AW`i`U zs#a!|MwsQNhl$^Ld{k+{Hs9Omq;7U}QqFoKggFEmaAoI$y8s=7{LeU7+5cH0n8Y^z z%@1`(kNiL=f^necH4wygiJGdoT7Dn_DK6`ccG;2bZI37TUy_zN(^!3Gj<7jC|*j$`1 zQFP`L-^)>dNh5@i06GI@{W3hGCtd?vuf7>Fb1i%BmADFn#OQ58HB7Dh9?>%k_A@ZP z#V!U?_?a+iUK=94nf3Cp62rIF4uzHi;x8vn?sa#tqv%tN8$}fZHeEcVaLM5Bd@=~i zS}ZPoh(mccP&~Qbhg2(WGc>`!Q!qqCjBFe?UZ0XM$YgSj1vO6uL;DRu z?uikjhuFnFf?L};-iCM^R2b&m4g2em_Kj4mSM5wLrD#2@(HGR=o($l%8&frSyu&^N zc3Uo3RtAkhU*ItRBcMPnG{Jo;N;zI-hcfn57%8>$*m-bWG?=@C4r-|_7-Zo}HMlR* z;&+Yx_$FZ?a?1^LKs#(;daKDkj?rk}Ed&+#Dc<6JK<;H*bN7fUjDAQlbg8$v`S#jP zSGa_SqCd#MCrZ$DgUH8hT1nnrfvL!QMgdtpBlT@pjtZPRkd1{0{K#?yh(bgTOoPBM zqsxd5@>w_^?I{;{`A|(FNp=jOQa#R_IyC~u@5e{4Z3cEghW7c`^hBic6k3TB z8qUeK@=8;IDM_(OF*d7HkhdNeg`4a3!7d-dL@M8Ay6->oCfJ%qKO{7TCU{cXlII}0 z{ld3?1aVDr*w-IaC>7c=4F=(GMo<_4-Gxh)D#YvW6!Ac~rhwT955^wHvn5Xp?M7`G z2OauE+|=Yu3x#t#M%}TKW)VdZGC~0+kx4Rz7EBLzk!KU{KcwT!L!OdXVN4>yukSMr zobTw)Sim6xtPp+(ais6?$D$0__%rUXZQTPreZ&m31=T?&%|xmI zy=q~sFHmu}^Hdb4^RZ;u&?W-E#~V}h7w~Y398OO5PoX`?1b^Wy-A)}v9G;d`MA|%` z!Rr@+?)X2}-WbuesL(Q<1+~3xoXNKM&A;-V{W-iqL#g6yu8mWOP=|H{xh)l5>vC7e zd8q3f9xTo-6X}$&Ff2&6JNX%DkhRnDb0H@->gj7Y?^LPBR_o{f-)bm+uY>WX z9d}AQ{_#*WeL8=6hq6M+%bZvy-_OP4qi~WzP{xb8sNN#Q>n&u@hybx}8!rfsChz5x zQZ3Mz4Qqg&f9qtl-55Lc=@*7SPnXwvqzsNP4>4jJ;WA+ra$6Y3@zXtxgTMO*rnwt{ zisZ#%<3)zXIbghAdX2#^0Z=74vs%eKKP-<9K3M@DXx0d@?<9<$|AZyAGxbgR&n>&= zV)P3BOfjT}BA3+ytVZ{$_-O7tY&;u}W=y)0n? z&Yz7)?2&<+3h+B>()1JPV?K|MTCEoA7MfEF zMwd6gt7Wh{h+uz87%HcVdW#WBr(R^3Y-}$hr098q8XzYiCb_e6h7N9Y0sYB5`3_)v-ZhiwMB1c#*584k$1nT|56G4BX3K|KvvWONyO!c|e;6l3;QIVg;{qeY{`huKiMcVeNBQ_wA?eB5 zvB_~RGh|iw2?pQe*FWCOl*qv0KuhqIJghfLp~xqd#F=)0Io(>kVdv0Dk^Ab2@g0J} z>#E-4t$4*c7++bci0la1r*OlAUm(=*nBR@SkkGnoh}5twnI;~ee2~5Rf_)UYjQ(gQ z6g~2A^t6gc4{NGy)!ve;3ba0oc~MG5eRjJzX_;uhv*+~;BtN?k6Y_oZTy{oiySx62 ze)gv;^!&@E!d^RDGx&{=-U-UAY1_yFW`({p@zrW~J6K=* z#l<+|0ny!$Pm}KCOuu`Unu=Cc#5q%V2j&xccJ}glqUH@%l93m}wJ{FI#!43E>W^j9 z&1!QeNb0gwU*sqlq?z|)MPmBhHcSzJh?pct&zyVk0Q(l<$I;pGB2UlJ)ws?v#|`&= zv4y)R$GWU5VXlfD3#&KQSrbo&5(Aj_lUpvIDC?v2LP`Vn=E(Pvx9_3UPg#e6?3efs71`#n3jBjs}3f&D!Sk&EE~erD2J)ZviP1$ z$bH5h0?}1GpM!Pwf+_qxV=b!^r*9Bm?2P#>09Ew2Z#xq9U8IsX{Y07sKhhQO7^j_k zybr7{$^}7m#ew2HW84kI=*e227S~@{fY@uBrze1ymmgSSl8t-67CAIu*#W#9!^_JS z#_ri_!4v$QfrPMW7bALS+~Ic)dLBHjTeAI#vKQi%*4ixDW3sbhnKh8`q}uf5%$ShJ zck*Q4o0cTjoLjJU%WKA&uwJ6kCj_iX$=grjR_LRnWW7S z{&Gn*T6O%mqL;G1fIIU$n|%{`Xuq1?f1lOIVtC6tOEU+c05qA z{QGz}>&Z&wf@Xn@b45DqOrp56&%OszDP5pd@rHrFQ?6OyyA@M);urFMruo>`>-jW^ zeiQi>U*YDx)@x@#fU!eujCU#wsZtdK9Ni0o7r3s%)foMZ$|WQ16W!g+>clMh^s=JX zAJ3lj?Z6kF`Qe{C%s)GA=p8=|;<0Jp-b?o~s@AIDVyGd%i0Xkaniod9-fnP*v`G`r zG$Am(#Oc~PTQtFMujy1-zaNNq0Gk=W9InI<@Aw(XC~727-J%RLfiU@7IZW)I(`8j4 zFk3z+h^4bgZ$w!I1#UFf&K0vA$F9!p+ZnqdC(Y=X==}7A)x}%H$*+{PYB(V`D37XE ztZUrzvz>0P$kJESMi>6xPGMh?3Sno1D267Jom$MkLwk^=-tx1x9tfqn2GJK0H;BSS z8l*jbVQ>1`ZU$qn8J6R43}7C|1>^1+Kf-$V{=SJ_k=*xcPqVz2FG%AZSZhjaCW(IA zRwf+4KUmH2MLO%1%%9>xCP&0Ozor#dEmA;VQk~Fz)WzYaL}5=(>BwvfZY4Pv${mW$ zMDMk{Jr{_OW?sEho4shk18ITVN1pHHRY1HdU6Ds|EM*dvn1&izLQKOw(?xApEZ=D}2QmC&`B{2oxuEadt8!f$dYGn4^R)w;= zW6{MNY9IGB+`=ThbxXA%kpA1YjE;ot*YX!Ygy3a$@p(6?B`u$EgbAG;oAHJfz6{ER zN$_sSi0-$0%V!KwV-UoyZ%CDSQ6vX$bAo^*#1h1(xo|)E6KfKdM2vQRGL(8;lh}JA zkJ1Km*?s}Ckw0F>;J|GtZeiaX;TI#PWsk(maDAPFiUMNVL}(zUU36n0aRl9;+w0)A zg!3(j7GGKr(@wCxp<(U%yIhb065>iGPU9HpXI^6Jmb+t}DQEpHKj*%8Sivt#@pUY zkrt(26<@6O6W##nc)~kcboQ=mH`>w1U`S^@)16`sdJVhcO zs*8=2R=|@=&~2|-??Bc%#;s*jRV<>dG?iM1u-FaWR{a(APDqq8%pJSf@3Glfu6*B5 zNeYzIFGng)A7VTl8L8goi*1V3Lkgw`Uf!#;^3bG{C^8v{->?&3N+XRNL|m^}Yr5Zm z!1L>83FS}rPQ?^cwY}O?_oI@cmn1nnEN-y;we|Mn+o)6VB=6T52c8!g)Jd+*FJ5 zFoeZvg))jQHt02n?zL^|>nm^jkw2H#r0cF<&eP88AHhpc%ycay3DPf(sdapI zc+YJMeK*|>`|77S(x@FxZuHNn_gqj-;*C>_ZHlLO8#3p&;b|DxqP9Rb#RikPAz6j` zYm#*W>OP|NUiLIjr{`2|C+So9sVlvpHbDK8)-fw@HFNzHA&#DenEc9XlldZ^M-^(| zyUV!1N8a=8w^=A6HpjALrpd^yV5JtkvA!<_sOE)Ihi?s7SZ0^B7LZPoHpl9}#?^^y zMGV2&#Yg&K5WUR+#^4pa^G&0EG!xdrw%e1bVENf@G>QD|f~(OTH0EB^?YRc8s8)|_ zx{rD$dzb<+tBO)JtwFqM`PH0WG8xz{4jep+ubW(#-gB~+)c}Ch8B6b@h9)-dm`fw8qsMk?$ z;DvI4zwL7;cotajJ0?BJ7*KWbog!@B{Uf=C^WQAJ)HXP>NQBYbn51zqS$MEtucmZK=1D|qV{}5!6$zw zm`jAu(^a+}vY*ruJ9#=QnX8|jFgDR%(mWwp7flSLs&*I%% zHqG^LkX$@-bFh@uoQofR3KHa0@9dS8UPqtcS^i{5+J1jWe-qLHo!wq~5l`zhSvO;H zQ-WOZ03m2?bmTl~?7e?V&#ZmRJju}2uK6xAjfLYX+XgNNNwcGO_bNII%s2KfX+=yj zYF3V_?)Q(DpM0{azY+*-7xY^v-;Crhk7yPIifIGR{>*9=IB{KM%L5WQQTm(?Cs4jfWI;z>iLUj>HL@ZuqNDjN24siKxMn zZ=@1GSIy5d0G6hRSX9uGEn|>;rch93Y*d`k zC%;CRX(Kr#g*-b8g;<6vmpk9Ite(eGhMw(HeX3s$Ni7@1&htP)30j(2E~?|qdlEZ} zsCspSFf)p{Ifg$qED9K3kg0MUuaKJgO$DWoZ(fb}?=G4XY(!9h?s+3p>?C=^WH>@b zv;Hc5j^m{(Xf_SzI~d(jOy?+{>>ilKn76w@m=l#EN@W+PEG_%(iu|KX*j7$VFynB; zEi7af9BOM-tn?L{x4NeBhf^MuGSh!Rhb)VsBQO((-|IJHUFxS-n8R6Dpp(9qbpG1y z*EWUdpxlt{C4;|zbWNq}L`O|cnJFarWPjB&XSG1fbG-zS6%|qdxNm5)&P3Wo z*k8uc(N1I^Q`8tO6>OK8S{Wq&ISVWzq2KSDE1POHpWD-xt!LUHzW^|@a6;ZA&DN|9 zsYr`R<>&ab&G70h0lgibyy#|mTU=s4P``<2?G+qck15-{MRgg6FhcI1){o(COEdKr(D#Qg<0V(#*H_k)lBvmM+q zKc_L|7esV#@I&M4AKtnvqK+hB`f=9#1$>YRhTW_N?}?7lTv=QJb|-rt;Y}7Cw$mz( z4%L0-cek(Re@gKctB^b-wrrhXq2`zmFIjfU*`BjZVs`+J@3Q<{qkoq6SK zcH?|TA6-zyiO`L}_BpYQx~lhAYKxWhk0)`2Ts8{Sn+WpnuC!p*{JdAw4K<=CdcM85 zj+bwv;NoFN$BmS&UCohsKK8JgN@=H(T_M_7vA7TSF#GmIdENOh@+2>ONk_&FN=qi% z)Gbyn?o2}GwFfxqF|?S!5@=qu|D%R`kwuliokW%f~i0NNBSUb*rR$Iu~~?)!6x0MFTMuw>UzJ zR}Gl8VuXsL;^-I7`PUEbd0>cZ9dXaIc~Hrc_f594cA$DP1HI$pp?$M_)-1%cmzQqp z9pI*hE|7)|3_lGCzZg86mW@98_~?BU7TsN%hfc4KE!Ychsm3`-jE|hpXl+FBmP22- zlo?DRfj;wQbX8Y;!tTIY*<#^x@`7JL@Cfh zm~a`inS4kfM<*u5R5=ovvQ3M=EGay`dIoPC9yTod4{t%nH4C78MJ9L?1r=W%G7?!v zJJ(j=X_c-;HJkc#%K9`d4VRJXQkx<_RF>=%;9&3$08KC-3GVQt_YP%2AYP@sq#E;SKXdB7LAD?m*z8ujE>oG67hGD)pTl%RAEhccV&GfZrJq^=eA``|;G4g%hcX~mWSr&S zE9CC;H0Gvps^tsMaBgmC`v%d-}3GMJiuy%szXjFfWriv zg2FmSnDSHk>yf1cDQGH-ND`1}lgmm5m`}MMXQvQ1W-$6qdOl#wF%aPsY&*FvIfx1MV*ANbssdOxNN!_JV zT?oO|GPq-bRA7_l#f9La(pO{jmCw|x4PuQT71&9|uF=`*GRPTwDB%)zp+kp~Z$!uL zK7{aA5eXoT)XhEex6vdTaNhuPb>p60I2N>P^Wi9`Ppi}oMS(yf2tLOTq`>6I*43%{ z*6UJ&F{H=1(o_{nz)@l=9bLeHKgN`FBgh^+W^Ab)m4BP~A}uLkRt590y4fFd*Dl4& zhO^(fqGk95<=0hSeHI$_?N7;o@!+qL0pkJnpOS&K+200}QKZ3S2pCK_f>9HHLBbvn zn4;=Y0hvj^G*VC9Ex4Jv+w=`MtC8ok{yfU8h(wYeMSnj&WL;79300trw)f{Ff+~ax z%rUO44lLz&sL0bjcWuhJtQjPBH5V8U?0wR^@<4H$W=*Gr5E&26& zcx!HcbOhVYiA>hh!@RpS3gJ2i9Jy@A^I(ua4c$Teoetwd))477vX2|Rb2e_uAkvQc zBA&nTnw^lO@>Qgr&SX=_5*cYvpUko>k>Eu}+GXU;jkJ_5#Ux7wr8U>hkq#3eo^BpL zmicBsj9`}|ObpLLzpcRVtk^6fuD=DIe5IrA;~@Bf)KUNL^(j~;$9$1<=`j(Edzqyc z{63AZSMA%MNINvA_m6h^o*Aea6v{GUT#VIL%~TG5dLVn5UH{#oYe;(%C#6o&-88*z zF8xrQ@9~3mS#uwxm-_Nhq0VpkH3-4^Po0`<(2cSfDXpG}3YJG7iS{rxOJT(`MXIUq z{-dUr$(851%l*Rx)v||a7u!zO7>_vu0n+VTZdi7>q84wIeAfo4)d$Hm`B4UUHxG+H z(BOdjz>gi4Lz>u9K6z??I`AO6QE>4ppT?D#6rSr~1Tyc83532@Q2)|V-OqCR<`ZP_ zWaB%KFJR?jN-j(S)YpLvu+5Re0%mai>!HDjnq6U)_#YMky)y zwIv@NFK4y+Bj`1$())%V*=17kaRWL3WxWBL@!1fvG35=UAmuTQi6N>a^@%F4o53BB zfnOUbqWifX=i5x)QP=Idpn>c|3CXC^3P!V_ZXgh$OkFNG>DnFAE;cWY=lp8XDM*yh zM)g7R+xsPdUEe15;3;KcA!_M*wUDW{yzCb_yHw*Ro7Q`Ko4}2LVkvN5_&Wh?Z=Ay_OMZj_(5K&jLjHE75X}SpGI4>OyUOb5>HL%tck5qY{$={%?8lk*`|^ z!MgUrSL!ryfMk6^BqBt&)X(w(6!T);IwJTLbXm&xVsMSPN>=W`D0=hZOvuO@f-J=FNJPxpS1#)FplheS{}A7HFc}3tI*F;f8D(u%SjB`L~`^5jtZ&<0<@Hp}qPht)P*H~zjD+3uA$pZNG_1xd zPHvsgxrj3ua!FR&Dbn`=r8l5knZrXjkw(3hS!k(KBQhZ;Lde~X2C2&4kbz$0h=c|^ zu25A*7Gce97>g>^7VmmseDQmrj)0dOm(hXd5KWc%CmI|kOl2~(SkirCaeH$|FQ=Wq zz28^>1nt|bv*|<7GSVX@$lDVWB0IO#`3LaFAdFF7Q@Q)zwU>FJ*&q=ZGVGkzdcGi{ zZ(TLgh5Oqg`8Ss^B;Qy^b}E`9?a<21N|D3)tFXKQZc>Kn{f||Stf1TLk8VqgCH~)w zpJ|TW>9lcK-1O|dIclm_YUVKWtk_R=F&Y5mw;~P>8G?EVT=Z&}S#dQwHnzGltpB(H(sPg$>nT4`KR3|2EZ%=h$W8udK#;<)g9h%zMgIOY@OuBh^ zI=3h*ysSE6hZ{;r^QiHY@bPFSiE>}z^w~~GZH8wSbjTZfG3c1EP4b)IzndRxd!u^o z;ZrjsA$&zFMAk!mmPix}qqn-W z9|{F+AhCQ-rJh919NShRo?&e2qA_vIdTu9S;vs?;ZeWs)>e}VH4aUqQG%7L{DuBzo zI*ss2_g;Y}5(zo+wmcIBrB$3t>S__eJX9&9&#~B3erJBR=ZpFN?qAG(+6@qQ==0|& z6-qm=8}2HXX+KHL#*pj2whZqxDomcA@R$B(?I$10iJuoio%mq~Kz%eLW9&t#D-Lf% z6PZiJDg7iPF00kUX(zJGcE@#6WgHcSZW0fQN+C$$@3TgM(p&7cb4x#xQh$d@e@sdL zB_HPzg$>rEbIZj&NQda`xFG6Jlc2_`$DK@hdfx*5{O-#od>=g&QNVbMr+q9U--jYo zgd4W+WZ999&kSlII2Ug^sg|C*VijhnvqrpiG>;{*pjPOrQ>&VvULuG9T5O$q?3@5i z9CMocFI8vlb2Q4UW<6p-e@Ns1=g0=^2b7roa4*IEso+H@V#?kHB9gC-c{qDZU%pSD zFaBr->STY%J2nLZ35DK(H^Ss{WX}NmL0>9cT{a?aN%}^&mk~&w5Z_KcZ!*a%b|Q-DG}J=R^y-JYuE6=TR}C zUf0S`rAOKjj;eK9o%t5Ojoe(f-giw%d&GBLYGYD(`objyB)Q7*k@4aMA5^Lp1LRxjzVlN5jW(0>G?eDEH5sJ+Frw0<2*|N zGuM?x{;Ro`u2uvL0>q?1q`jQqMhd%k|J)l;Yx_-^ME zgE7vZxA#voLC;4_EmaH%A!6A7_MjLV==tb`Nvo%7h(0oHpY>IksDVYkd#4fY+BE$KuO9n?3|TZj%t97Z(Dqep+>D zZ~%=Gjcexw$eh=n&e5fZyM9LesEW_RK2DoozH3GSL(S6V_cQMsaj-@;`#1|< z`Z*H?0McD*#tsv&wWvF0R;f_8{)6UOrFCC!F3-Qf5?1vJT#%cWQ7+?M2DP%lc3#$P z-hmDL>cni?9K~#KRV2{9+#R=*zU=#0s%F9D11#O2ojQyZ0hgC?i7(73{ps!xHzt1O z+<1-C39Fc^4jxrmdgF0E&%3 z77t9z%^A6&I*|g|W>kh+P=1YVxhp7cYQWm~8W%D)QLRE^WC2!JhJeeLA3XdmNUL0R zE95U`k8JNq3ZZdtpNdbuInz~@v&1zacs{&$aWby!9RC6d+KEM}F6VT*vS&lPaG%JJ z0??k|dcL&lm%^|f;mXm4MEAZ9w|oAS0$_9^NiUMJCNr2+=bH0X*;zox z$2;a7v5!u8Tku~Kw2#EDAK9L4t{=l1QqszYE-Ae1Tr!~W?%BozcVSvjw$6~B-r&3Y zA54EWfbC>8o$9=baB-779rrF@8___TdFLX_U#$KuE+P4z1CasxaQakQwU&@=CHtI&9)%=O-|9@# zecqGFIcSV&)5TFT8tFm67oga8NFu4SC64FT+T(ZSher=F%&pu~ti|QmD0Pf4hj?Rw zRQ_Lcezk2at`|bG(m^NcKT)BV`*^DNztdmfvZ5y1+;Gz(t+J=VqId$DA8pcMvLdGH zTqkWm|BU@unT&%srmrRLH#*_=?vw4X7iNwA*p9^8YhLb;iRt#&hAybSMFq_ac-Dqn zeCq?13c4|Y4Er4Z@R>3%s)}tcX>au(?;EzAURN%v>8dP6?vC$4Y;(;&U-!-VLIAII zHK{XPJJGFDjqs34h&KFBdOmcaspBzOk=~8GXI&iHdGPq4_N-XjSo}mUJF>IKocJ#Y zKUJ?p{11fRb-v$p&qVA8HtXHXV1L}b4Y4tSUNdI!A zPpq~{fm-W^cF0dR*yT^?;ZB=S=81E@qffv7{1ndvzHR(V>y$T&J*zHU{R`%~GKTlS zhr+c!ePlV}7Z!5sz(Oht1f_!&$YegxRO9j=sta-KVgTmfbd zGu8a~XHQQKakz=bw6FGo93`zY5Sza8(17u;pf8= zj#RG;6=h`G$j^ccpwI8uvjO7GO5cV$%FUigj!47~9r?gQ1FC=m=K2BW!nnZXEeKLO zLzFr(R;{?_GDVny#nS>a1;Kqr!-hAvhDn}aYK6-<)XMvztAeyX%SapNfPw)DvK8ci z@Nk2HPq7}PomMWNE`#~`1Ujnhn*Vg-en2fBn$LPbO=uMM>sBqw+d?MxM2w z=dTF<|AwI-&NzejaHhNKJ{TST)U!xQdFSV>E%^J8@DA*-8jxTDn|H+2yo3DATAn;B z4(6k!IDKX9znS{QnLpp-DK92>V!3~#bBTx=Y=e~`)W3kb$E)8zlC34e+=<`fk1P?g zx7X-gf_5>hF`r~>g#Nxfg0LF2cFb(7X0CM&v0h8{oV5--ViPZ1x8L+QFdIEdlehtL-(&l~B z+3PNIT>m?M(6F(!TH&vMoi+Vw_xOcs#V($UVPOabkaR`N$DqC4r0fiWk;lvEH$ceI zP2P&B_R5_W-k6(DK0xMf3zXmuA)zPp*A}>kNSp5(x`FgaN0lcxoAO822$ zyUN#Va*DYrWbtRwAP5SDU}w`3{|pwq(!X@1#bm)!l1=eTNKNC@eZP0t%Sa%BxYQv4 z;yty`h8A%elAERrufNSTdyirngY|ie@h7Q&F))9}w&gBHu9!XHd+dE_1b1COO2<9e z$u1ooU&N6dMtWeRksg??s6a1Dk3wyOr%269=IVRhawVkw<+6R_|6t^A{Bko_1&TMn zVgZhg%{++=JphdTVZ+h@SW1up8{ltVqAq(sOV!Ixj>O7!8|4d$3O)W))2><+Qht^s zmkb;mAI(n3fV!E$&r%}~O$%g`P%L_G=1?oDFUERhuW_htbB$kvGqi?NJzNtWCjMAj zA}QX~>}~lq@pdWl|6^y{h$7e_x( z&PU(=iYdPQ&xH8sWXV0!WtF7Qo(KT)ID_e3KDf25*@6{DIiiIl(4eI+>Zl8Pp`ubl-*YeD>2k7 zg{ax{b&eZ^-yBrGzIhO@>r|`nwA`$+wb%GFC+`8N?la3UrOOn(FPY|3GnBWcvlXwD za|=}>$mY?sWHWEdMjqJprTjwP*w;)&-GX>E^+l3_p|z}CaS4-C@ONg^Fpy5 z9WEWH&{uN5CbcAGT5ea-jVPA8XU$`{{=X*OD}uSjpVNzNH~Xzyl8LT6 zPmOQ~Z%|{5A%Tod!Uf55f+Pcb$+c(VFgtN{uLfifN9|Wb^}?J7H80Ub=)Qb4`pBEfdEvp*yJW4^I;S|I2QHE<(*e7xyip_JJxDjQnt%+yuo~QdYMU zASZ4+V`?OGS<`V>$5GF+1aSNceX9q0e>7p%U0G^7+;-#HI(@22l5@6Nu~Tx;13cpH z-^ueMzpkK6A*%|_CQ}2y5}UHq`1JCTgcV)NPj1=Ie-Kkl2eRQu3G+qy_>kLP&hm=VjC z7Nm|mN-=a;Ja8`Cmvs{de1%-E=-i-eLV%wJISL0p?L5{QVUY}$*{Em1zx);t{-hy` zbs>VBE0Lflgl^_uZVfBx=~U}?Co$6vHH2D8VCu+Vw8FFZxX;c-~j{CwYI9(O=jhbx1JwLc;2zSb?gpSSpbHE*tdTlehGdywOL*~-<^`< zA^gV&J$;q#Zc{gYohjWBbg4TQ(WbTby3DfQ(|T3wjlm@F+YM6l1^WA-`tvW=h^TWN zuN-p>KLx%8lj1!3sXiP(c|5Xi%QXC_9OzK3$N=ibmB_EGcO}WY+Mj>mM;VvOr9@?D z?TUQ-b;bP+L)QdZnDc=9Pt0@SOj+hIubj4q;OV`#F4C~CxS(C5?8CoDYxR=ZG-5cM zty0!pHhQ$;_2TEW^O7t}W>)QVr>;{>YZ4NOgvRcA^rKkW_SfI78j0QE@#SBNuGcDg zLq?{{gr0l*i**@a2IQNZH&G*qKDQxV;pEZhtNNBv^tS+#e)j93svk0rt_o?4K{0qw ztdJiGq%gnAs%A|v&5Q0~18`2ad?@ehzW zzf(opT`y?a)|VNC&&Y?4NJ0VI(W1kpNByRtvE*^gcQufNBP|LRZdX6BMJYB|Fs`@+ z+USc?zb>QVe;HPuJ7J{v$rl0iYZRkYl&BUGEBx^`vI_R6lpw9WpHBnPVnH zip*_#Ir>lar2MA!yvXj5>>cUZ3@20V>eC(V8{Ue}gjjA~JZO0Olll}ma6_zTJt!Rk zUa*&5?I~XNPrPIL5VM9Yx4af^T@*nC4?co4>AgfQvK=zflq!!;3}j^UoX&+dGdmfS zM7$p6;e@iyU*Zb!FqGTsF=;FsbgAX8Iy;KPi~zUhfzxy~@UNECMKD`%YD~SY`)I11 z-#+M&l@x?(u7yCQvv@?PVJslhkJ@CT{{R4Nf}~8LT6K(@VC%vsiFPHgIfI{2G}xGg zF$VM}LTe)AW;UBdNffgMk-!)0k;kRmBkUECm1HzngKZbIa-@DF+sv@BvZ*MU8A2K> zd=|C6RJ}3Vv1coxvD^MbU~|ZYTZUk^#O$y6|7iZ0l6SvqrzN(CA<%6Aq~qI;XG)~O zXGxY3@zNLa_1^Q4?G?92D}QX^#LO@xo9>rnBh3bc*tJ2t9bS`oJ5C}4Y>o*P(fsm1 z=rl4#bq@&vN{Ss+A&87mcdnNwcIH|#l_vtK-FF^R|G(wf5Xj`3By`tH@jgz|4rl}U zgtLT?P7n(c)H0_ytcFpOf-~;oZ~TE_X5@o9g~uJcKOB38AlA+=SI}YYOWaXN^&B!* z1@`#Bpk)R}*+Oi{LK~7y%NF54BrrJi{jI(jM54SO?{g&W=Rn|=HK?OM?^_YtjQtyC zBPpTEV%xL6z?Y;DFFaiY-k4wm3pz<2jVQR!M|Xlc9%kLlzA~7H(_UuEQF~Ub51QOJ z$2Xxt7-F7ytG`6#oL$dVf%3(djRn;UQ{PhZngtyS+#6bj8p@GX;A6QZ)YnDx&LC2H zscr6}1I-Ro%TFT{PekQ>Ics60XxsKcTR=4iBQ1=jQsU_k?Il~_Zeg>qtxVgxSgF)w z_x5t!)5&~T0ClhVHS_A&@V5|*65Al3O{ovvBWpQm{Ni*0(`}AV)2V`C5GSb>!84af zt6f_<>oLBmSVRrTW7xa$tXw5}t-)=Ovn`OulwH3paXu=5MmAGS?2QCJUQq?2xjMD} z!+WP+J+cl8=ZoC>_=b@aF26`ETqxU2C>y3$1P853OeC<_tKGkpNcToAI8Y}#fr3%|vQAHxsM;%>rtH>Pw(hLcq z8M(DkZyUe6i4770lG2ERl%#|-2q@j%B_J&=-6sbpnu-AIlb$_1gCRI~KHp>>e+b&3iNi$%f z>AEL9h2h60=~C1hE8VT2$m+=hU-A4i`ZB;exjdITP3%VjY|+IeG(1172Olkw#wzOQ z!M}MR7Qwv6iLbKqy{U>kHHgAe#B)Yk){#uvX%a{yVal!k3fdl%M8l=lzkN$P%dH*} zMA3Y`u+Qe@YJ#wK=g7sFiMjCRfW04H|G8)0?gEZ#S}RCd8s>-@=l@lA2*^hDWlb+l%)m3&{>Em^D>gf$RP&kw+6OsjNgqT86l zGpr{Hjuu4S>7k}Z4BNh)fP6A!XnyzXERYS6y4qp5|J;5I5( z2KGicjaLyG zy<4YH3uMJ7fh0M$uxC;Jd(Y~h>)La3GonDRNmu9I_Iygld`!bwG)=f?P~!|+ssnxe z81<1`H4wJewm)aZpXFS|T&3Dxfw5XkwuE~MbAWyE3*EDW#WOba8E8S1iQq@5ZkMDv zqP}8t9a>1;GTBAOKy*?saR2xYcI zyO=Onv<4yMSX{`kpDIk+b-hE7SBT`#-OZlN$#FJ5?xxt!PQuHM@2iqFhGhO8&#i)} zE}O+);yUMhKSU-s>fw^a0pPmc)j1R(ME+_Hqq8jE-H`n3%^)U8AsXb!FCMf3McUXv zKT4i^+pDiA^sc)*{;50JbLhmsv30r2j?Dta(htswAad~MUGw=jxdK`FFqdB!`R+bS z*3LM4hAyYK6INoy2e|qL{#WW8sz3kl)H(j+ht&BTBNhlsbCX?Ntw%rv=K#%Ao7At4 znUQ7+^*;mJKAZ{^lc;m8e<;4H=1_8}od_uCr6(E~@2`#5wqLv;uMaf3mjfDB>gH)E z^vpHr`%7iG60G>KCnijfbaGa-j^ErJ9DFmefIKA4{}|D!ZDCG-1?)8S2LTy(dOl#5 zskSbGged#xJo$4CmQ_1{P`FZ|M#|kmmwzjJ$s7lOuoH{^5OcsBW2uF#$OBxGtN zy!`c^-ckj)=_XLafWltY5NqGdVl{vMkxK%$B=9T83xneh_OK>$)#Gl!W6^no9B#V9ACz;XX{;5?>&Bo>o)+R$ImM%;aFMAxpk_ zL&)!XiTkoI5Hh9z{CK$`GWh=78exdKXIcSjDO*n3HG3ILhyPC{#Kw3g;lX35ZIn!|SX!21LV zJC>lJCy*;vg0|4+x~>RVmbM)qKpi&FvQNM|W0HL7;EsP01!!pU7mBt;nmN>BQvJ1W z4Vaahv^}+X4&EHZszof5G&}!*xFP=#?qvMbZvhf;9DpnDKivL4kAyAlGEr@GZ@RIM zF!l)a@LuAthM+?PtAK>=cBK@u%(P0OxjzG;E0EK_p3AvvxwKt~=23r!-ep-M^q95{ zYD6zJ`H8qHuC*aW3QU>Msu6&G3?%Xhrffx*{Df^00wgaBG4Oy!mtLsjL*qnqc}FUn z)<-}e$|DY4-x_FZOXw4$3p7CuofSdLAPK2Xnoje;W<_T{!|+Z(Yx5vpZ)L5%zD=eH zizayqHLO?vzmAiqEueCggg1oW`AnwiAu0GYwhz{gcaZ@xVLCMnQfGL!1}sFU?G`}=9> zUS}X2^GruP-5I)8CEXAQFW7FFkKiA;-Q`?RG};+_H95@qBPhCegjR`OS~~7%5?ki# z9njPg8c7!dnp)o0HbU1OI*FUU^YwV;yLV=%J`z9loE~>O%l)-289LTFw1aTv{1ra8 z)4jsYU8C5I?W%ZO^~EN+8$@BL=pC$L1dA%`Ks58=N8tG`G|pKfL^Y3+sL?{Zj(0 znD{#5FAxr>)W_3%)*J1|LVjtx)l+jFnre&fUhQ_!6-iZS=H|T}9|l*zI9}wyt782- zM&4`1IxefS54^o|fU?bif(&uj>+haQn2mT*j6qOv{h4ItAM(AMm?jS;-+*BKpV+wU z6=fVY-b2gF1@mL4UBB4i78u!U%D6FnC^|pk2DVpY0Z4h zGkgsR2Ro^>sDJ0caZ9I#P8RSY?S$q zdFgYVs1L9L@tFm*pJixnqR=A`KfEQsC$>S`o0Lyr(-wVebm1evaHLpZJ_A@!0HFxu zWWe&DT_a2(;gy2!&;BOe%wt*Ugy1OevVPz*>l_VV>`5Yv$%e?6>YjneZJd!l7T7Xq z9h}m5s?OT{CZ<9(KrhP&`u(@u{8NeiI6*`Yv5N`dL05Zu3ixH9*n5~T5-Obm_UHfkOjC5MptPc0zO`IZC=ax|bk!I$ z8R1{Cb(D82?{`Z^+vXm9t+R`dC`JiNL0iDOswuQd0|YZ>R|ZhQOeP?h!G6%6AKuQ+ zFAEAzuubBeeJuyCaM}5i-bc5Mo0QY;v{wnw6MdSWEL~o1a%!uUuy1D)orFC~uG0vW z?tygg@dLhf4lgOA_dSrAeI>*FWmK(PNfyGcEo_adBHRE3B*Oxc;cyK@+TTQ*=WI+_ zuL#=3a*IgBT)0dhuAY&{MXCu!J*yi&Na;)Ev)Udd1uhR_1H1gIc2AYoF5`&m6g=zue~^X#xC?sG98??GXd8>=38|<^g+S+Y6V>Pyu3?;s1cx6iAw**x)I^ zlHLlnq(g(>tkIf7wCq8D1;hVNjRQgQXk1F=$XeT#Pv(?wA57=4vvR(;yx36Fc{+>- zpodwWNH0|skfuVdgWe&7I~E~~A+XcH_x%TW6a5C;z5))t9XXZ8L>4HmRHa`&`nRwZ zDti@~r4zzHe0ghK?WMRj{3LP4AVzeTPf6h*z)U6h%CiRwy49zGKl;0cguwxwQN`fW zQhw=_TbfEYD^=Q8LRohh+%yp0uG=`Llm7Uq!qSu>9|(N&q5)EU>c<|R<-EI_ig;kA zUlAq_)7u*}cxUOLth~jFfi}MMG+#^+!7s64-b~-DsO27hSvXnyn|tG?P$mz`Fq*-< zrM-S;|65%CC%erX4P3M9A6j3sK}V4pKr`IB|8o$tnIv>!Ow24hVi-e`clLRIS>p@Z z4cK-pQo$EG+qTk?N^+9j&{>;1a7)g-wKDJ5gq_LKHGN&|Sv*uPIoQG9s`MUFGtz;O z5+7)LDcU;rg4kAa&ipGI0s%yjb0YyL=608|PFi{H4wKFW*3MgY5Eo4|p8HEWx(K*H zcVk31+cP$5z_fW1P?@lFQ?An9o>)(6^9Wl;(bG5x$F)1zY=h7%y8Vsgn5 z{c3z$e7FE!Y&j2lI26{oIIgrVl9F6nIkMrD0*|@SS@WlteM&?nU&TPt9dfJVE9;6{ z8_?UXUSFt`Zb`w)A82iL9ySMB?>t5-eW2E5W{bp9>qCxpCZJ7^W8_Pe+voNz2F^)J zdO^iBDahXz2^`Y0ZjNaP>kwAAS`Yy)jUk1Rr@AET#(Of;HNZf~PPwej%0Xle!{4vkG9tHua#+|R4Kn| z)D}nNPtPc)2@={*AMuJX z9%X1B2#&f*E9b1Z@dA1Ev%srtU`ar7Drb$G<)Rzqf1lsOhO#4Vgn^6?Gdac51@{iD zoxI0hq@r!hr`b3SU3M`;%IcMg7qZURb>soK&QJ9L*SX-e3VqtiC)fXja+^oxH3|Zh zJ0#QMt5=~*KhCsUEBub#r(-LuaMKPCQJ@MTyMbHndlUZi%3l`wo&#U_OVO54x~ak_ zxCv;0S^4CE?bFYvjG}(UbjX@hxaF5s>=SJTFCS0bR-WpZ3#Xe71B!;zu2jvko#VP8 z&{Xpv4BBE)jttMaIXquH!S;*-g7L7kB9x1>P@^|UY*^DmZvBSBZgic>+0=k%L$}o< zW0s#{BP|FfP*xqPuT0(lVN$2po~T~TyGk|2$E>rg0Nbd-tkjSzRFeW<=6p`g-}yTm z7T?8I^u>>#b{9XCjh5yJx2*^Kw#Uv z15yC&dQ5mZG%r1pr890NDAVCt^=cu~)$#dfkd@{$15piT1fKsdP`hW7|OrRqsbiq(rZ)!eg<_qF?FR^T) z1;B2C0jEW&UBbF>`Rjq(f%r5+Bb98ArAfPWyuDly&{R_!$3U@UyV=ysB1$(JK<@8@ zHSf~hdam3o*dMBT6sAdbhDE@tousK=E2VBu#X-r>Dy!b#)zKwNT4xE*e>k@yG7?`PYK_nhkkA+KJA59=29H4PGed|Dmv~MfCbs+OoM2=@`&|q)> z*gp`J75O+z1&EI}o!nn)bi0ocTI5MYUkjL!;KR+Ib!MtwCDA;8h6*VMEKV1#J*xZj zx4m3Vs2K9xv&R4iP0LWWWOSd!{kUY(heiCuo_rG_kX82xa#-oX4X2&n@6iSY6KW+(# zH|6;O#!omDMgPR6s#XN(HU#a-X*giLD5>2t%Ww~F2=ROd4yBvGoAP9_@kIrrq4mR5 zW_ti3&2b0MbUY%|?{)6XO@=V)ihZNl1Cj&~U4A>5;)#olTMPs>ie{Y462YSn*wGq} z{3vKdgI^RxrD3FG|8v6nSsw3l2lZ4dp(x?)izQ_i#Uie77DJ8#G z^iYnP2_=NPxs)*(4eyDOM8+FL$K!;&bCAGRU2ld3M#9wWt0ZF6dlVl>bZskmN_PRE z`bZn>h(b|4+T4|i)5DtYK@!sfKKZ_h@DU*f>On^aJm~FZY}6Xf%_WgJiq@&==9BOW zTGsHWBu9H@XpdmiM&Qu79dA(KH(=LEo+5_Q@u4@+4W`Vq`4+e*U!5Bb-#%S3b9~HZ z?2FkgCj)JUNuMRPOY@UP=2Uf1;2p2M3<5n3HxOeZ_l-16&i}}O)#fXar>Q`5u`w5N zROUS-B)s}V4jPF5e-4>-isc<4-i?#dQM~1qjI(~@ig)cX6y_HnvrEq9}paSS3C@JTK{SS#o0R*sVP|x5_7|3Zcu;2 zu{r2DCe-+p<-u^7i`WEC=98VLelx%98I{0=+s)s&(7YI~G`edmg^`D25dY3l zbEf>Ap)S&ok}|lQFzw(xOHg=9j^qUOc|n;la>1j>;D`Cn+7c*M&cTTK)oil=db`R4#WAIF8@9gY#O=ZM?%#C>$ny!1&R_PYs*4zc*K1QS!uzfPWb(qo8%(8T%dSHdiC5A}#I} z8Gi>e1;xjknKC!1Mxyzj)DNG=^ay9~{1i_^`8Lf7AstXMQ#%&8gsM4;<1y*wqNjtP z@n|UQ{hg0iX9kM&s+cW=yg)7Cs<}{EOpS>0nPP2`BkJ$?bM_!$rXyAlCCM{(yc|7( zR-fuVh}FDTw4fzv{R5zTd9OD`E2}b5H6tjVH7r77(u7Lv9J2$RxHF+8_ur6o8W-fl zyx1SVoeTy<=EBV!p}b5abHVtfNr)Xb2lfgc(wiW9KCE$fN^foYnpdx*#kF#|^Ui0G z^n-bswsoM1@zsE#80*ftPv6v-5~8qe&BdMWFUGh+c#$dojfsD$$V%-rI*I0b3_(}= z#Mr%(Xp8CuF3+7JIhfGSnv!9*$`ogqvvoHp%@rBkP%i!<6|Ms%R4|zB6Ucq378bGe)6%hJdw93;@$gxG`7~Zm;3wl zP#Fnqdv*u3ZX89pY)c=nq_Pgw8}FS?&Rc=s%P+PVC!H#GCoQW*+47pSueQ)$~gHs znhgh(y5Noo>1A7yt)X2uqnho2~I zTT0~_y@A~#Jbp3M*CM}KHoUy@lM8>v8t|4w&(+mPu;qSveFV+ptZHb>OC#d1u<3%s zm=1d1c~~BMJ~qEt7++ruyv|+V1Nv>Iv=1y8(Q0=zv`g!6W)N^YwL2{^7-Bd!QcY_; zwim{E+Vjv~6Fcc|=g2s<8hgjb0je~V4f%$zXcRR*Oa=o=RAV`+e^SjZL(&f2!}_c2 zGjj22qlnYcCpTfkDd@ihRI=fhSxF;_a`fmg)rj}V{&w^I85Dp|-#Na9g5TlnmLRg< zLJ-3m-|fMb!_Ug|(a2C2Tm7DT6W}km^8*YcpFDHJ&HtPSIuV(>W$fKRL8w#r0!5d;`P zbRSaCzp=6VcU?($joV+#|mYBRXkUlt~uD|F>R|VI5#q{sS9Ep5*^+9#M4U zAN_ZXnxOAn3tax+G7<`I3~NJ#%1{r(9^|2}T`XE2m9{|~av=UJrIp)I2Ee<(sSTa=m*o@fPp4rTmr zm&jNk;2g=l)rZq{UDP*ZtM4y)pK4h+*|2#zsL`{Zl+ zs*ehZ#5rb-G{7LvF3ab!a8!sXp@xK}*dZbtaDAToETks^03-O3SAW zOedjJnG;y(^PxhZjBBK01?mRr8NuE-)?NP$H*r#5NNE9D_SXU^m>_{bE$3s+3+&Z6U@bgk4~?r4>h>0L1l_hG!eY zIU_q0#dbC9kn$U_u)%{ERWYTiXD;1IeFW-I>0k=GmDI=H=pkK}l&LzjYL7x_M)RW? z;Ov{T8Q@F4gG#dM_?7RTT6*ON`=dQm59p|GTaXT>Yy5ocEXH^-Jeace}HFG~f-1 z6zA~l8H1}jO!0*`qn`Pz$)*W+s1*HNMS;OYLZ#(^8(qC68i_UsqWFHYzIp4|h@w+9 z0n&upY_F%BoIIIELWF!Y$bnHuo$-MsKaN0EkNo24RkY)~m1cTmVHju=blh9AZH9_q z=4(61>Qt=@=+4WTWJ;qOWuT0?h|XHd+U?RV5~?A;T}+O`ug$-> zyZ*6kHz(pqFCdO(TOayj%^-$i<5eWE^t`cWB#LK{8{;&yZ#v9~c4Zxc-*5RGZ^BR1 zLy=#`L)K4^BmmHl1IwqVRCDQYj3qyL7K#2jOa>5s*cHcQz7rd(U0Pi=0o4G~)(Buu zWY+qknTCw2nd)r>6gTGrX=#IGBeD4ce$%fgW+=!U7Y_cJW;yNR7Jc1s$6cc? zyT4`S@OQ1tnn!K6JO-n)|0hDW17r)%Gi~jeq(ERKwk%UX&`fBZma!AQ^!d#P-JD=uAQ_QOd zIEVX<$5w9Ksi0o`wYh5IUfj7wTaquii3s6nK<)bbAm4jt2}q|21$tOi^a+{hsFfIA zcmWEjS;O?BMfmZj+dya$uKaaa`bsQxg#mOO7C?wa-DlyLez(Q6%j{F^Ib1V*i~nhs zsO#-H5Lu+VbVVA3I&vQZ!%ZAmgQ)bShZdPWSQ`3;Jev4?WbD6=RW|AO0a?a_-n!l9 zW{0k=k0lytZ#w>kSvjvGG1jJ3V*Y*>IuA3_R8>E^#!Q3GJE1vY(+>mu(H#I_PMoLG zUzmq!mUG%-K_l~6GRJ;LIT3{#9i?@w$xLOi>Guv9iLoPIfU?(?gpiAx?9D#zhJXG= z^_Jb}KzBNr$um~!^CJ!wZLCk)cbCwb{2(zv&!sQxWhcsu!OD`oe6d*$C@22tZQnOd z*HlbRjMEKEvu|{Fx~{J^K36(5Q?nt6CRBjw zjkfGZYM)aj#e>G5wtWd>sjoL55xr!VUB&D7x+r!jumSP6KL!g%Z8pK{e3LJgez#D= z_(ex@(K&H*BY#H{VY31k@!zB4lBsXPvMs;mm59DuNE^(S2ibZrz*4DDCGfAhTDIH@ zm}l7>8xQwiT#mu~xGKa6=4i7YPZ*Hb+L0VkrK)*?+bcpa(z0Idnt$LB9PfuCVrCbI z%NwhTEc<7V$|utw`?xHRX+tKq7+<;clWUO3rjgtY%QaJwMS+SFk7t>&9r|nFy)Zq{ zT7*s9%KYl!$Uu8d#ds?2Ve1-i>f@I@zYMf9NiMgn>pq_;%~Q1&f-FtstiKqZHJ}ud zK$8CfS-G-H7-TKMMoFzW4y^B%D9%+Op`ZS`J#Hd6-XKTkq9tb+&}APPuOa;_iV??5 zyjVLIN%I9QF0au#mIHKi8Mg+6V!UL;r`Rc5JkqA%a zDGq`~_9`QL*8v(OPP1*dpxOB=3xN9GiLcb1S!N!It^j^5^&Kuf5n}B;m_N^ZXUMeF zu-mq9_MqY9YI01QFYzs2caZOZ_XmDPOSXKgj-fMdD)5tHy2s*K4Rk~5Nxe+(#;G|1 zlEY1;*Vhx{n0QYb4Di9_?Zs&uoZ!R>r1IBv5#7=9^w+n)CJ_X7e1bJw&uwP14$kFc zWXaTbkb{cltz@`)9lQ^_@!7gY-s3FBAV2~v|{=DXGux5NBuF}WG*_1hn$wkm4 zf^L_vyNpOJrTJw0oykp*`&u=Zm~8zZ%RuQ8t>k=u&9iQ13{0GHN2M93f@kalozb$z zqU!dF_m%0d3;L20wyVx7GLJPT$HpO9Eo(m#7VuURDh=_ja}R0nBop0~)lS>hxsOq4 zo0!XzlLfvSl7&c8a-Uaxm^`>W3H~HGRla?ddnkCi5qz3P8?$t0xDSeht^N$^hsOi) zJ9of71r=?=r@zs)qw-asj%GpA0AZEaM%u@EkmPxc@E`p0ySy0dDB^X0#wJabsSyd; z(Iu4ZsG2xkm@^lOl~Y2+1eI>CHvK9zsa0ID&dwque&3QZDTgY>1MEGFPngiZ_dH);<#M~VUFsmxU`wL zo#GiKj7}e`@8%~Oh=p?S9s zyj<$Cf?hA*he>#TDHruELU|OXDxDPOp)d8StLW~tAUd_6>+{OiHwo~J;EOh=@W3}r z**vxKPe8Ws&1Cmre}OkiP^t}4yDVS_Q5-+Nt%{iVyiScbtM|I#^|NoYGPh-JI+ioa z=^~raJ>>6V5S<;hjr`nG0*fUKBz5%A$@XiHO15t_=68bmzr_2!xU)MCRIztJN!22XhgI!Y<%W-Z@-{X|`MP~{i3NG-QH z!(KONE+DE?$V`!OK+)j~caC1ckyv}zVLDFr!rk~B%&TW)?>)%jKIx+~G9?bkq@|@{ z^E<$DFvC zNPt7ZM*b${FW|p1#p#K=I-tj9tWZtOHM844kF-eo~ zBF0c6)ukqQAGNRZFOcE&f3VrqGeP`iw=ihBu!Kov$4_Nt(JL{CsqA#;g-xIO=D^%p zp;Jh=68Tl;H^~=c?Sny6Cm35VVyzgT{E`Jo$X&dP! z;i6zEa;P%GUi5pwh$QlJCh37U-%!+AcB?3m1{3H9*N{1|(_VO*Ng@()f^E;iGN`a! zE!{o-%(GdH!^fyS(FRN!kmAOiMC@{NlyAo!KTV|P_D{-=4RAUP)T8X8yA*tIl<SFq$nT#KY+E}~`VH$uJ?%>}D+rq+*MdTGKlH_?geyLcjJ6}n z6Ejc_+Tf&6^nY~B8~pg#J6ezSqS^_DXLNjPGTb`9M6r-@&7`xHxHptGk5ee_T-8hk zIabU}_&x?{BZ_Ozbd}w}z^y)0p^&kodU-L-urng4^Wjv%I*NLr%xZfl+{12M_kPtGIvCqkmm4sf)`+_94$i-rWr!9}%2i2({yqDax0wPmrsd}2 z>L#M;gUeC&x=+s+w&zU|y2y;pUm72CL^TYf45DfX4qf({ag>~R8xUh8_}XNlR<6NX zZ41p@D&JmQUX@(rdA?N;%<^&ZB< z7(M3?F6(Q<*^WcD0cX;HJ1OnvA!=IZ`Oflnu~V}deA?`YUheHmo;QXVbHBxfRD$~Z0_STX zuKnC`-%0Z`;0C(#vMX>eqFO9_6+ieH#N`LU_W-BGnMnm~bsegfFM70Xln_16IGF<7 zk+tLOM6Eq>oRzDOA2|bn=9gx^HEl|jSUEF?TC~9`llo_BIgQ{O>&ta;m};L(0B|e? zBqNz@>?6n7P5N+b%0vvNjjJ6iaa*516+}=&k9G=a5^En)9uUp*p`?*z5dDthtYU*H zI3Ar6;`R2?MiCZ2`b*E*S;zZWmczd1tQSAyKXI2HI^9g4jhEUl+e|d16Wo_MQf?I7 zS|tlaRVsMV6@vrbg9I^pqi){Bw14|ha^Y7$emt>V}VX@=jw>(maFsvqde>u zgwNiW5yelyK6$Re=S&utynu6~tGnB(N!yDBV)iY8aVVmb)(CFVG!c7-?wQJi(I<6) z?A8RjoKs%7`&oXNt9;vpHfy<-=`qrqDw)3I_aa!QUDJf-8M-$xcuWe^kVwT_ccPzi zt$HwJ7mRG5o#(@i>UPG@$n_&JxfycLW@*lUx<#=Zw!CQJh7qArK&5ZZtJ?TFSoP=`OrFy2zr+ zr`(it!>MzWXjCB?F|QGz^*zk^$SnO`5`$ZyEmF7cajd34P1t#+IA09M$br&_tzDML zjBg29Pc9WVmY7cUoFu9j5sI)og7Y`I?FdoWO0*w0|KKxo=~v+%FKWPvlDWB1b`Ggt zq$z&xZfOKuevSE@g1UrGKV|u9ioS2B3fjnjBzH_U4e>Z5(g?4HyK{lqh6ff>LF(#| zI5UEZgUDFXf!oZ+{Qlf#8JyRcZOK|$gI`v6r& z0r~r%a8r$;DYG)GWpGP{iOn61yLT8_6As}Ch?UO7$%^0S^bs{e>yR=dyy(F4e9AbB zJ&)9Ma7L(U9yJWf%Qcg4nIX3wRid~w9Cvyc&FiZT|g!n{#@?XQ&yUWMzIxmqZ+#i?QynCYV zhRQcI`hgIXvag#VcSm5b4$-51ciqWYhSy?10slm7PV12~S|roWjxVDsl7P?GYP_WY zGsCzE6y0)6uSXP?3_d*m-A~=l1mg;Q$OOBmQGf@#d|)_ugpFM{Q*KVHG@^*F$vwPA zn7weGh8ORgZ4CG_L?*P2dl`4O)UDt`W5%-o&tByarw-~mU7t@c)|{$zh+JW=h65;p zv~m}=DpIhhiBKfB`a(}i?q6TI>BmQjyzZ_5i*#~(pFPDoHuI!)PEm$Sf6jKaEFw|j4JMa`Y8Km{NrSqb+uR_JQw&e8=cS*66A4T}W5jojd zax^KQ#w}0&`_eMI*Y_>2|{O2`RNlcJkqwr0C^|Xd-pI40EUd#Vy&^AC{^W`wdReo-#b@<1d_|%T2q$ zy3%FP2l9f9C8ONuKjrC0ztR3sIjk6oSTm`ElN5;(W>L)(5gj6ME0j5IAYc?jp~X;Z zG9XXG;fqMq+n}bS$IRU5siQiez=CPK2!zLZ+f-U}p_Y_EyPeS-D*=D9^b%76^|EDY z$<#WixG)%2oB7FLaO!&*rWKUn;F5OgIv~4Xjba%dq#+R8ec;>PK3@D`=(am!^tHqx z(fk*5lCtPPlFUaLrZkX3syC+NBp-;h%nUO+sr)t&aMYWmEW)jY9)>*qJ>(KkX>PeT zRiI{UyTb3#+;Q3_=fp-{N>1)%o`e8o6()?w=sd3Yg(A<(%fPTWBO;qOmi zB+}-TCN29pVDn;XJD^QAZf1q3oS@M|&?-A6F9@VpU>cU$W_k0di(4K?ppEU@(}a)# z9lhRnHEzGRD2)OT(}xJZh~N-;%S>~lm@rIgomQNcO|!eSzC;7`prA{$xgRlzN=o|vujOnj^ZA<1H$L|6GtsuFFne`Zr#Q) zUS}wMc_L)S?tZQ{bAKRfaPNfCKkz8!*6meOlXdVRYu65Vk{my*zg{tfDZj{<4BX9Q zqU+f(3iY=B~A^O+-0jO}w6o@%CcdN#H|O+*Ln6 zd+y$?+~!~aOwHZzxRqYAbhl3o&Tg!QR}r(C{|;Hm^KT;9;6`?S1}0mTQ=@b%8%LEg z|D1#wt=tn(A8()_F4$!J0dp2Xg9;_6u(399gE_r%LOPO*uztH=T8wJ?W1>*?^PwOj zkPLH7DFgd9COa3ma`M5NbIPmx4{~RmI=Hh4n*SV2T=Of{<-_z^-8|`hNkb~-ka?8S ziU3tM*-meER87>{dOnuc*zrz31HJ5=bmRtGwTuY=J-$a|my^E_td#&p0m74RytgmE zQLhk)(yVX#r%u2gIzp_!f|kBmcQ2%{W7gQze}^vqAB&8OdqoPmv3Kg9yKZxRXgH^x4N}u>*rl5!!9mo=>GCM7ms` za%8IOOz`UP9hY>Qf%(l}1r#}lvJz87hTGsf4(;9=H+8;1pg(;HQ*6pG?#5HRq11F^19CTDxLLW>O5fq zmdw^{kDDI+3~evT{>cfjwFCWUqefs=IJ{r*7ZyKe^0Pj3#5zZf;JF-fMrSt zSv-5L=X|ccr{1rx4P_-Co&y$)F{7e5p)W420k6ogX2^4NeV?ck=lBk&`t-I=BgLQD z{t%SX>$sTXC}u*FKuLW&j8Yb7qN3ZTcG|_f-MjAlDbS9&htUGaf~cX~C&ftTW~cc=gMBhjN)Z#=lpt z=7v*pgIu-j;0;%5O`-&4B{`Hhl>%4W7P%TPh!~zZVwrE91^c7}-#z|J{XIh(t2@lh z3w|UGSPU7A5bzsjgl~JGY<>QoYtP$A&nqb%RSL#??sBU|j;>pJkQlK+n3ne|@f`hK zzxC|#f#J3rGqeYajGh4V1Ss$N%%%{6v$F?B61(r0UonXLgrXS)tE1DA5?+!v6!X*v zChNR_yk#tbJZ;(bFXnN^;7SKLs9RGcZLUvKYI%E6{hf*3%J<-YHpVh6iME&@xP?K_ z8r(LfDiEu%W-yU=wm`|LJ}kN*kZ+_th=XhrCGEJ=|2U>Zt?v2t2!jImq8Nn=X);SPjY;tNuHrS z&{ozWzS@ei?`WWo=&u5ItaBfKgI=04@J;;{8j>+W6$+fwM7}R06UBC2>^`g8^YyjM z8s;9wz*lK|Ti3%_({d8<=nZH7!*5u}o!9V{TnzQ56(h`2%k00M(m)|JQ9`BsDn+{f)o}2a-e^j~zUSL#h0XK2-dXFQFQaYyk-C=X~-HU5j zKl!?+b@sb%=98{%Y_D(jdylm31}f(9N(9D>XG>l=4}OT|yRn0FE88f65nCXVguQo1 zw&I16n?sl5O=1-W4L&Oqu0>HW;Q!p=wa0+ylmt4(J2-hhdNCGu(;kzWn-BfD&d^Pj z=V42tgp>86dTjtuiy&iZ!1zQRr_W|Jb>y-(C)rcDBr03YBy(oX75^~LrGx)G(Hq=p zTC=z6%wuBk{qihR^tW&)&I$GC2V0z4tjiW4|0r{Bz$65r;bX6=_l=AuhydL`kN_(OMKzy=AkoIN20q3v&<+#=STt4MF@@k#b4;NHNtJyHZ?;We3uZ9SEDgpqu`KhiHx z18FXe%lC2EI0ZN>xb@hD8{$+Rb^)>~U-j9)ovCvb|F|IMju|Z=(k&a_lI_vBxwPZi zj$%9=>|A*q1Yn!Rab1v5%?nySC$-gp7C&w9Yz9V3|n~ymu5nKuyv^+HnBu)6&CUq0zvdq0N#6y zmjy=r>@E!p8US=QKIi1DU*s|=exd)dBWV*!)t5{Tsv9MixL%=ue0i6@=4qVb6V2=JEwW zk&=I=6r27Js@(~6P!})0@8VYS*PTJ+L3Eh0wfO6V%cie^-b+FOC_0-}ps#6FnpR;F zAlFm!r1S*l$*(XV(eN1SE~QjB%>fBkqs<-#=%QYeu8pH+lG0Z}~V zM(zi*v`ttW&^MPQHCeisJ!&mJU%Ok~YWQc)xd%sG83$6Py1&)?&7aVsyx@S0`8J{*UqRCqWiBI$ggtMw1q&4sj9TAq+#J5(1rZlBshc1N(ga7q_h=%h-nT$7L4d$X#r zTXg;+yN(co0f6XpIv7UhE=?Gzhh|^0ZxXt5i_BdtmvP=rS!wFN@@4$Zm;nsCpeb$c zZ%ukaWDwn@@+1i;Xk0pxOjxId4wQ0~F7s;w81UO8#@HFsLioxYyb~*M(iMsol5l55 z#eud_5C3Ab)5&s8*z4!@HL&omWM9U~ue;G1O(fY5HokN`5adc{PTtB?Zaa)t!6|sq zL!|?Ecx#S#sK12pm+!3kS)_1*$!X_Rm9T+GOTFTkAMdKT(bDF17T%fZ(dKw3Z<+pl zSNJw;QoLwC1je1{TJ|Ka{U1sjKfgz1$cLWf^1X2X{_fiew0;_YhC867S4M9Ml)k>Y z2L6MC*MNeD;S*ZtJ}vPS;=8nVK8DYIdTVd7)t}Lq;q3b4NPh2~9|$c*F`Rb0o6lz0 z;SB$5lr}GKEm7Dz%^oLO&cJm1qfN=`5lo{U?8f4Y(t5)E`%jc9PFV=w-^hm|vhjww zPu~%YS-7AjvMVg};SRvN0ug(bfUhxP;Q$7w8VV{heu*la2Lii+s)C1zE4S_`lelGD?h9Zwy5 zL0x-6=wqavGJ<<`&vk_Nu2SMV%u8-vYv>to)1tigc*9b(`+t-SgdkEhhe5upkpZm^ zt=0QUYMh@O1bDNellsEpVgH(tT6o{+nxT32d5&I(&ASGNL+p^C3cQ;D1 zdbrfj*lxG7{zkw5L=zj*7GJ#InM_M}C{WP3S}$L~6*l@S5k&3@ApHFEbpsvVR*5-Rd$} zhh&C2vWG4ZWKoCcs^Rhku>qwPzqkO z=MXbgEI#Gh1Bz$gWMUME$?q#x&^A?;mKpU>;;w6Zd>N1ge7`Dii=bHnQ|bcppowxO zi5rb*P!9lz0S?T(tL^s+Aztx`u-W8ON)x?*4!CPD%e19CEW=S}7_MZ*8X9#f-?5vu zb`$K@_THHRkT7Yhv~3m8*syR8=QYi0iGckcm0mC76HRjn403@3_{(41_elRDu(&_c zuw*;*7)uNabVJD@H&5L%{;#b*Yvz#nb=`tMP0+}1^v0=+|0W|1;~o*bx$4-5&G1p* z4&_Z#6O;5?PI_@DP1&zmvv4FO#u5iui5|r literal 16689 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 diff --git a/media/gfx/game_bg.png b/media/gfx/game_bg.png index 691bd5453c8125655cd8fde064d73c8f363b1fa9..5dc42f7d313526fb73b3b31503b94a96d2e6a39e 100644 GIT binary patch literal 14793 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 literal 10231 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 diff --git a/media/gfx/player_body.png b/media/gfx/player_body.png index a082434a0f6a0cbf8b83c27254bc3d431da5312f..b13e599cc23631323f5cdcc27170ccdb5f912849 100644 GIT binary patch literal 9353 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 O15%JtkuHB>68K-d&I~~S diff --git a/source/animatedsprite.cpp b/source/animatedsprite.cpp index f39ea0f..a6d1985 100644 --- a/source/animatedsprite.cpp +++ b/source/animatedsprite.cpp @@ -24,6 +24,7 @@ void AnimatedSprite::init(LTexture *texture, SDL_Renderer *renderer) mAnimation[i].numFrames = 0; mAnimation[i].speed = 0; mAnimation[i].loop = true; + mAnimation[i].completed = false; for (Uint8 j = 0; i < 20; i++) { mAnimation[i].frames[j].x = 0; @@ -105,6 +106,18 @@ void AnimatedSprite::setAnimationLoop(Uint8 index, bool loop) mAnimation[index].loop = loop; } +// Establece el valor de la variable +void AnimatedSprite::setCompleted(Uint8 index, bool value) +{ + mAnimation[index].completed = value; +} + +// Comprueba si ha terminado la animación +bool AnimatedSprite::isCompleted(Uint8 index) +{ + return mAnimation[index].completed; +} + // Devuelve el rectangulo de una animación y frame concreto SDL_Rect AnimatedSprite::getAnimationClip(Uint8 index_animation, Uint8 index_frame) { diff --git a/source/animatedsprite.h b/source/animatedsprite.h index 380089c..f1c84bc 100644 --- a/source/animatedsprite.h +++ b/source/animatedsprite.h @@ -5,9 +5,26 @@ #ifndef ANIMATEDSPRITE_H #define ANIMATEDSPRITE_H +#define MAX_FRAMES 30 +#define MAX_ANIMATIONS 20 + // Clase AnimatedSprite class AnimatedSprite : public MovingSprite { +private: + struct sAnimation + { + SDL_Rect frames[MAX_FRAMES]; // Cada uno de los frames que componen la animación + Uint8 numFrames; // Numero de frames que componen la animación + Uint8 speed; // Velocidad de la animación + bool loop; // Indica si la animación se reproduce en bucle + bool completed; // Indica si ha finalizado la animación + }; + sAnimation mAnimation[MAX_ANIMATIONS]; // Vector con las diferentes animaciones + + Uint8 mCurrentFrame; // Frame actual + Uint16 mAnimationCounter; // Contador para las animaciones + public: // Constructor AnimatedSprite(); @@ -39,26 +56,14 @@ public: // Establece si la animación se reproduce en bucle void setAnimationLoop(Uint8 index, bool loop); + // Establece el valor de la variable + void setCompleted(Uint8 index, bool value); + + // Comprueba si ha terminado la animación + bool isCompleted(Uint8 index); + // Devuelve el rectangulo de una animación y frame concreto SDL_Rect getAnimationClip(Uint8 index_animation, Uint8 index_frame); - -private: - struct sAnimation - { - SDL_Rect frames[20]; // Hasta 20 frames - Uint8 numFrames; - Uint8 speed; - bool loop; - }; - - // Vector con las diferentes animaciones y los diferentes frames de cada animación - sAnimation mAnimation[20]; // Hasta 20 animaciones - - // Frame actual - Uint8 mCurrentFrame; - - // Contador para las animaciones - Uint16 mAnimationCounter; }; -#endif +#endif \ No newline at end of file diff --git a/source/balloon.cpp b/source/balloon.cpp index 01a3e2e..e6c84e7 100644 --- a/source/balloon.cpp +++ b/source/balloon.cpp @@ -5,7 +5,7 @@ Balloon::Balloon() { mSprite = new AnimatedSprite(); - init(0, 0, NO_KIND, BALLOON_VELX_POSITIVE, 0, nullptr, nullptr); + disable(); } // Destructor @@ -15,29 +15,34 @@ Balloon::~Balloon() } // Inicializador -void Balloon::init(float x, int y, Uint8 kind, float velx, Uint16 creationtimer, LTexture *texture, SDL_Renderer *renderer) +void Balloon::init(float x, float y, Uint8 kind, float velx, float speed, Uint16 creationtimer, LTexture *texture, SDL_Renderer *renderer) { const Uint8 NUM_FRAMES_BALLON = 10; const Uint8 NUM_FRAMES_BALLON_POP = 12; const Uint8 NUM_FRAMES_BALLON_BORN = 10; + const Uint8 OFFSET_ORANGE_BALLOONS = 58 * 0; + const Uint8 OFFSET_BLUE_BALLOONS = 58 * 1; + const Uint8 OFFSET_GREEN_BALLOONS = 58 * 2; + const Uint8 OFFSET_PURPLE_BALLOONS = 58 * 3; + const Uint8 OFFSET_POWER_BALL = 58 * 4; + const int OFFSET_EXPLOSIONS = 58 * 5; + switch (kind) { case BALLOON_1: - // Posición inicial - mPosX = x; - mPosY = y; + mEnabled = true; // Alto y ancho del objeto - mWidth = 8; - mHeight = mWidth; + mWidth = BALLOON_SIZE_1; + mHeight = BALLOON_SIZE_1; // Inicializa los valores de velocidad y gravedad mVelX = velx; mVelY = 0; - mMaxVelY = 3; + mMaxVelY = 3.0f; mGravity = 0.09f; - mDefaultVelY = 3; + mDefaultVelY = 2.6f; // Puntos que da el globo al ser destruido mScore = 50; @@ -47,35 +52,29 @@ void Balloon::init(float x, int y, Uint8 kind, float velx, Uint16 creationtimer, // Establece los frames de cada animación for (Uint8 i = 0; i < NUM_FRAMES_BALLON; i++) - { - mSprite->setAnimationFrames(BALLOON_MOVING_ANIMATION, i, 50, 21 + (37 * i), getWidth(), getHeight()); - } + mSprite->setAnimationFrames(BALLOON_MOVING_ANIMATION, i, 50 + OFFSET_ORANGE_BALLOONS, 21 + (37 * i), getWidth(), getHeight()); + for (Uint8 i = 0; i < NUM_FRAMES_BALLON_BORN; i++) - { - mSprite->setAnimationFrames(BALLOON_BORN_ANIMATION, i, 50 + 58, 21 + (37 * i), getWidth(), getHeight()); - } + mSprite->setAnimationFrames(BALLOON_BORN_ANIMATION, i, 50 + OFFSET_BLUE_BALLOONS, 21 + (37 * i), getWidth(), getHeight()); + for (Uint8 i = 0; i < NUM_FRAMES_BALLON_POP; i++) - { - mSprite->setAnimationFrames(BALLOON_POP_ANIMATION, i, 50 + 58 + 58, 21 + (37 * i), getWidth(), getHeight()); - } + mSprite->setAnimationFrames(BALLOON_POP_ANIMATION, i, 50 + OFFSET_EXPLOSIONS, 21 + (37 * i), getWidth(), getHeight()); break; case BALLOON_2: - // Posición inicial - mPosX = x; - mPosY = y; + mEnabled = true; // Alto y ancho del objeto - mWidth = 13; - mHeight = mWidth; + mWidth = BALLOON_SIZE_2; + mHeight = BALLOON_SIZE_2; // Inicializa los valores de velocidad y gravedad mVelX = velx; mVelY = 0; - mMaxVelY = 3; + mMaxVelY = 3.0f; mGravity = 0.10f; - mDefaultVelY = 4; + mDefaultVelY = 3.5f; // Puntos que da el globo al ser destruido mScore = 100; @@ -85,35 +84,29 @@ void Balloon::init(float x, int y, Uint8 kind, float velx, Uint16 creationtimer, // Establece los frames de cada animación for (Uint8 i = 0; i < NUM_FRAMES_BALLON; i++) - { - mSprite->setAnimationFrames(BALLOON_MOVING_ANIMATION, i, 37, 21 + (37 * i), getWidth(), getHeight()); - } + mSprite->setAnimationFrames(BALLOON_MOVING_ANIMATION, i, 37 + OFFSET_ORANGE_BALLOONS, 21 + (37 * i), getWidth(), getHeight()); + for (Uint8 i = 0; i < NUM_FRAMES_BALLON_BORN; i++) - { - mSprite->setAnimationFrames(BALLOON_BORN_ANIMATION, i, 37 + 58, 21 + (37 * i), getWidth(), getHeight()); - } + mSprite->setAnimationFrames(BALLOON_BORN_ANIMATION, i, 37 + OFFSET_BLUE_BALLOONS, 21 + (37 * i), getWidth(), getHeight()); + for (Uint8 i = 0; i < NUM_FRAMES_BALLON_POP; i++) - { - mSprite->setAnimationFrames(BALLOON_POP_ANIMATION, i, 37 + 58 + 58, 21 + (37 * i), getWidth(), getHeight()); - } + mSprite->setAnimationFrames(BALLOON_POP_ANIMATION, i, 37 + OFFSET_EXPLOSIONS, 21 + (37 * i), getWidth(), getHeight()); break; case BALLOON_3: - // Posición inicial - mPosX = x; - mPosY = y; + mEnabled = true; // Alto y ancho del objeto - mWidth = 21; - mHeight = mWidth; + mWidth = BALLOON_SIZE_3; + mHeight = BALLOON_SIZE_3; // Inicializa los valores de velocidad y gravedad mVelX = velx; mVelY = 0; - mMaxVelY = 3; + mMaxVelY = 3.0f; mGravity = 0.10f; - mDefaultVelY = 4.5; + mDefaultVelY = 4.50f; // Puntos que da el globo al ser destruido mScore = 200; @@ -123,35 +116,29 @@ void Balloon::init(float x, int y, Uint8 kind, float velx, Uint16 creationtimer, // Establece los frames de cada animación for (Uint8 i = 0; i < NUM_FRAMES_BALLON; i++) - { - mSprite->setAnimationFrames(BALLOON_MOVING_ANIMATION, i, 37, 37 * i, getWidth(), getHeight()); - } + mSprite->setAnimationFrames(BALLOON_MOVING_ANIMATION, i, 37 + OFFSET_ORANGE_BALLOONS, 37 * i, getWidth(), getHeight()); + for (Uint8 i = 0; i < NUM_FRAMES_BALLON_BORN; i++) - { - mSprite->setAnimationFrames(BALLOON_BORN_ANIMATION, i, 37 + 58, 37 * i, getWidth(), getHeight()); - } + mSprite->setAnimationFrames(BALLOON_BORN_ANIMATION, i, 37 + OFFSET_BLUE_BALLOONS, 37 * i, getWidth(), getHeight()); + for (Uint8 i = 0; i < NUM_FRAMES_BALLON_POP; i++) - { - mSprite->setAnimationFrames(BALLOON_POP_ANIMATION, i, 37 + 58 + 58, 37 * i, getWidth(), getHeight()); - } + mSprite->setAnimationFrames(BALLOON_POP_ANIMATION, i, 37 + OFFSET_EXPLOSIONS, 37 * i, getWidth(), getHeight()); break; case BALLOON_4: - // Posición inicial - mPosX = x; - mPosY = y; + mEnabled = true; // Alto y ancho del objeto - mWidth = 37; - mHeight = mWidth; + mWidth = BALLOON_SIZE_4; + mHeight = BALLOON_SIZE_4; // Inicializa los valores de velocidad y gravedad mVelX = velx; mVelY = 0; - mMaxVelY = 3; + mMaxVelY = 3.0f; mGravity = 0.10f; - mDefaultVelY = 5; + mDefaultVelY = 4.95f; // Puntos que da el globo al ser destruido mScore = 400; @@ -161,35 +148,157 @@ void Balloon::init(float x, int y, Uint8 kind, float velx, Uint16 creationtimer, // Establece los frames de cada animación for (Uint8 i = 0; i < NUM_FRAMES_BALLON; i++) - { - mSprite->setAnimationFrames(BALLOON_MOVING_ANIMATION, i, 0, 37 * i, getWidth(), getHeight()); - } + mSprite->setAnimationFrames(BALLOON_MOVING_ANIMATION, i, OFFSET_ORANGE_BALLOONS, 37 * i, getWidth(), getHeight()); + for (Uint8 i = 0; i < NUM_FRAMES_BALLON_BORN; i++) - { - mSprite->setAnimationFrames(BALLOON_BORN_ANIMATION, i, 58, 37 * i, getWidth(), getHeight()); - } + mSprite->setAnimationFrames(BALLOON_BORN_ANIMATION, i, OFFSET_BLUE_BALLOONS, 37 * i, getWidth(), getHeight()); + for (Uint8 i = 0; i < NUM_FRAMES_BALLON_POP; i++) - { - mSprite->setAnimationFrames(BALLOON_POP_ANIMATION, i, 58 + 58, 37 * i, getWidth(), getHeight()); - } + mSprite->setAnimationFrames(BALLOON_POP_ANIMATION, i, OFFSET_EXPLOSIONS, 37 * i, getWidth(), getHeight()); break; - default: - // Posición inicial - mPosX = x; - mPosY = y; + case HEXAGON_1: + mEnabled = true; // Alto y ancho del objeto - mWidth = 0; - mHeight = mWidth; + mWidth = BALLOON_SIZE_1; + mHeight = BALLOON_SIZE_1; + + // Inicializa los valores de velocidad y gravedad + mVelX = velx; + mVelY = abs(velx) * 2; + mMaxVelY = abs(velx) * 2; + mGravity = 0.00f; + mDefaultVelY = abs(velx) * 2; + + // Puntos que da el globo al ser destruido + mScore = 50; + + // Amenaza que genera el globo + mMenace = 1; + + // Establece los frames de cada animación + for (Uint8 i = 0; i < NUM_FRAMES_BALLON; i++) + mSprite->setAnimationFrames(BALLOON_MOVING_ANIMATION, i, 50 + OFFSET_GREEN_BALLOONS, 21 + (37 * i), getWidth(), getHeight()); + + for (Uint8 i = 0; i < NUM_FRAMES_BALLON_BORN; i++) + mSprite->setAnimationFrames(BALLOON_BORN_ANIMATION, i, 50 + OFFSET_PURPLE_BALLOONS, 21 + (37 * i), getWidth(), getHeight()); + + for (Uint8 i = 0; i < NUM_FRAMES_BALLON_POP; i++) + mSprite->setAnimationFrames(BALLOON_POP_ANIMATION, i, 50 + OFFSET_EXPLOSIONS, 21 + (37 * i), getWidth(), getHeight()); + + break; + + case HEXAGON_2: + mEnabled = true; + + // Alto y ancho del objeto + mWidth = BALLOON_SIZE_2; + mHeight = BALLOON_SIZE_2; + + // Inicializa los valores de velocidad y gravedad + mVelX = velx; + mVelY = abs(velx) * 2; + mMaxVelY = abs(velx) * 2; + mGravity = 0.00f; + mDefaultVelY = abs(velx) * 2; + + // Puntos que da el globo al ser destruido + mScore = 100; + + // Amenaza que genera el globo + mMenace = 2; + + // Establece los frames de cada animación + for (Uint8 i = 0; i < NUM_FRAMES_BALLON; i++) + mSprite->setAnimationFrames(BALLOON_MOVING_ANIMATION, i, 37 + OFFSET_GREEN_BALLOONS, 21 + (37 * i), getWidth(), getHeight()); + + for (Uint8 i = 0; i < NUM_FRAMES_BALLON_BORN; i++) + mSprite->setAnimationFrames(BALLOON_BORN_ANIMATION, i, 37 + OFFSET_PURPLE_BALLOONS, 21 + (37 * i), getWidth(), getHeight()); + + for (Uint8 i = 0; i < NUM_FRAMES_BALLON_POP; i++) + mSprite->setAnimationFrames(BALLOON_POP_ANIMATION, i, 37 + OFFSET_EXPLOSIONS, 21 + (37 * i), getWidth(), getHeight()); + + break; + + case HEXAGON_3: + mEnabled = true; + + // Alto y ancho del objeto + mWidth = BALLOON_SIZE_3; + mHeight = BALLOON_SIZE_3; + + // Inicializa los valores de velocidad y gravedad + mVelX = velx; + mVelY = abs(velx) * 2; + mMaxVelY = abs(velx) * 2; + mGravity = 0.00f; + mDefaultVelY = abs(velx) * 2; + + // Puntos que da el globo al ser destruido + mScore = 200; + + // Amenaza que genera el globo + mMenace = 4; + + // Establece los frames de cada animación + for (Uint8 i = 0; i < NUM_FRAMES_BALLON; i++) + mSprite->setAnimationFrames(BALLOON_MOVING_ANIMATION, i, 37 + OFFSET_GREEN_BALLOONS, 37 * i, getWidth(), getHeight()); + + for (Uint8 i = 0; i < NUM_FRAMES_BALLON_BORN; i++) + mSprite->setAnimationFrames(BALLOON_BORN_ANIMATION, i, 37 + OFFSET_PURPLE_BALLOONS, 37 * i, getWidth(), getHeight()); + + for (Uint8 i = 0; i < NUM_FRAMES_BALLON_POP; i++) + mSprite->setAnimationFrames(BALLOON_POP_ANIMATION, i, 37 + OFFSET_EXPLOSIONS, 37 * i, getWidth(), getHeight()); + + break; + + case HEXAGON_4: + mEnabled = true; + + // Alto y ancho del objeto + mWidth = BALLOON_SIZE_4; + mHeight = BALLOON_SIZE_4; + + // Inicializa los valores de velocidad y gravedad + mVelX = velx; + mVelY = abs(velx) * 2; + mMaxVelY = abs(velx) * 2; + mGravity = 0.00f; + mDefaultVelY = abs(velx) * 2; + + // Puntos que da el globo al ser destruido + mScore = 400; + + // Amenaza que genera el globo + mMenace = 8; + + // Establece los frames de cada animación + for (Uint8 i = 0; i < NUM_FRAMES_BALLON; i++) + mSprite->setAnimationFrames(BALLOON_MOVING_ANIMATION, i, OFFSET_GREEN_BALLOONS, 37 * i, getWidth(), getHeight()); + + for (Uint8 i = 0; i < NUM_FRAMES_BALLON_BORN; i++) + mSprite->setAnimationFrames(BALLOON_BORN_ANIMATION, i, OFFSET_PURPLE_BALLOONS, 37 * i, getWidth(), getHeight()); + + for (Uint8 i = 0; i < NUM_FRAMES_BALLON_POP; i++) + mSprite->setAnimationFrames(BALLOON_POP_ANIMATION, i, OFFSET_EXPLOSIONS, 37 * i, getWidth(), getHeight()); + + break; + + case POWER_BALL: + mEnabled = true; + + // Alto y ancho del objeto + mWidth = BALLOON_SIZE_4; + mHeight = BALLOON_SIZE_4; // Inicializa los valores de velocidad y gravedad mVelX = velx; mVelY = 0; - mMaxVelY = 0; - mGravity = 0; - mDefaultVelY = 0; + mMaxVelY = 3.0f; + mGravity = 0.10f; + mDefaultVelY = 4.95f; // Puntos que da el globo al ser destruido mScore = 0; @@ -198,13 +307,35 @@ void Balloon::init(float x, int y, Uint8 kind, float velx, Uint16 creationtimer, mMenace = 0; // Establece los frames de cada animación - mSprite->setAnimationFrames(0, 0, 0, 0, getWidth(), getHeight()); - mSprite->setAnimationFrames(1, 0, 0, 0, getWidth(), getHeight()); - mSprite->setAnimationFrames(2, 0, 0, 0, getWidth(), getHeight()); + for (Uint8 i = 0; i < NUM_FRAMES_BALLON; i++) + mSprite->setAnimationFrames(BALLOON_MOVING_ANIMATION, i, OFFSET_POWER_BALL, 37 * i, getWidth(), getHeight()); + + for (Uint8 i = 0; i < NUM_FRAMES_BALLON_BORN; i++) + mSprite->setAnimationFrames(BALLOON_BORN_ANIMATION, i, OFFSET_BLUE_BALLOONS, 37 * i, getWidth(), getHeight()); + + for (Uint8 i = 0; i < NUM_FRAMES_BALLON_POP; i++) + mSprite->setAnimationFrames(BALLOON_POP_ANIMATION, i, OFFSET_EXPLOSIONS, 37 * i, getWidth(), getHeight()); break; + + default: + mEnabled = false; + mMenace = 0; + break; } + // Posición inicial + mPosX = x; + mPosY = y; + + mBouncing.enabled = false; // Si el efecto está activo + mBouncing.counter = 0; // Countador para el efecto + mBouncing.speed = 0; // Velocidad a la que transcurre el efecto + mBouncing.zoomW = 1.0f; // Zoom aplicado a la anchura + mBouncing.zoomH = 1.0f; // Zoom aplicado a la altura + mBouncing.despX = 0.0f; // Desplazamiento de pixeles en el eje X antes de pintar el objeto con zoom + mBouncing.despY = 0.0f; + // Textura con los gráficos del sprite mSprite->setTexture(texture); @@ -216,14 +347,14 @@ void Balloon::init(float x, int y, Uint8 kind, float velx, Uint16 creationtimer, mSprite->setHeight(mHeight); // Posición X,Y del sprite - mSprite->setPosX(int(mPosX)); - mSprite->setPosY(mPosY); + mSprite->setPosX((int)mPosX); + mSprite->setPosY((int)mPosY); // Tamaño del circulo de colisión mCollider.r = mWidth / 2; // Alinea el circulo de colisión con el objeto - shiftColliders(); + updateColliders(); // Inicializa variables mStopped = true; @@ -242,6 +373,9 @@ void Balloon::init(float x, int y, Uint8 kind, float velx, Uint16 creationtimer, mBouncing.zoomH = 1; mBouncing.despX = 0; mBouncing.despY = 0; + mCounter = 0; + mTravelY = 1.0f; + mSpeed = speed; // Tipo mKind = kind; @@ -288,16 +422,17 @@ void Balloon::allignTo(int x) mSprite->setPosY(getPosY()); // Alinea el circulo de colisión con el objeto - shiftColliders(); + updateColliders(); } // Pinta el globo en la pantalla void Balloon::render() { - if (mVisible) + if ((mVisible) && (mEnabled)) { if (mBouncing.enabled) { + // Aplica desplazamiento para el zoom mSprite->setPosX(getPosX() + mBouncing.despX); mSprite->setPosY(getPosY() + mBouncing.despY); mSprite->render(); @@ -306,7 +441,8 @@ void Balloon::render() } else if (isBeingCreated()) { - mSprite->getTexture()->setAlpha(255 - (int)((float)mCreationCounter * (255.0f/(float)mCreationCounterIni))); + // Aplica alpha blending + mSprite->getTexture()->setAlpha(255 - (int)((float)mCreationCounter * (255.0f / (float)mCreationCounterIni))); mSprite->render(); mSprite->getTexture()->setAlpha(255); } @@ -320,127 +456,116 @@ void Balloon::render() // Actualiza la posición y estados del globo void Balloon::move() { - // Comprobamos si se puede mover - if (isStopped() == false) + // Comprueba si se puede mover + if (!isStopped()) { - // Lo movemos a izquierda o derecha - mPosX += mVelX; + // Lo mueve a izquierda o derecha + mPosX += (mVelX * mSpeed); // Si queda fuera de pantalla, corregimos su posición y cambiamos su sentido if ((mPosX < PLAY_AREA_LEFT) || (mPosX + mWidth > PLAY_AREA_RIGHT)) { - // Corregir posición - mPosX -= mVelX; + // Corrige posición + mPosX -= (mVelX * mSpeed); - // Invertir sentido + // Invierte sentido mVelX = -mVelX; + + // Activa el efecto de rebote + bounceStart(); } // Mueve el globo hacia arriba o hacia abajo - mPosY += int(mVelY); + mPosY += (mVelY * mSpeed); // Si se sale por arriba if (mPosY < PLAY_AREA_TOP) { - // Corregimos - mPosY -= int(mVelY); + // Corrige + mPosY = PLAY_AREA_TOP; - // Invertimos sentido + // Invierte sentido mVelY = -mVelY; + + // Activa el efecto de rebote + bounceStart(); } // Si el globo se sale por la parte inferior if (mPosY + mHeight > PLAY_AREA_BOTTOM) { - // Corregimos - //mPosY -= int(mVelY); + // Corrige mPosY = PLAY_AREA_BOTTOM - mHeight; - // Invertimos colocando una velocidad por defecto + // Invierte colocando una velocidad por defecto mVelY = -mDefaultVelY; + // Activa el efecto de rebote bounceStart(); } - // Aplica gravedad al objeto, sin pasarse de un limite establecido - if (int(mVelY) > mMaxVelY) - { - mVelY = float(mMaxVelY); - } - else + /* + Para aplicar la gravedad, el diseño original la aplicaba en cada iteración del bucle + Al añadir el modificador de velocidad se reduce la distancia que recorre el objeto y por + tanto recibe mas gravedad. Para solucionarlo se va a aplicar la gravedad cuando se haya + recorrido una distancia igual a la velocidad en Y, que era el cálculo inicial + */ + + // Incrementa la variable que calcula la distancia acumulada en Y + mTravelY += mSpeed; + + // Si la distancia acumulada en Y es igual a la velocidad, se aplica la gravedad + if (mTravelY >= 1.0f) { + // Quita el excedente + mTravelY -= 1.0f; + + // Aplica la gravedad al objeto sin pasarse de una velocidad máxima mVelY += mGravity; + std::min(mVelY, mMaxVelY); } + // Aplica la gravedad al objeto + //if (mVelY > mMaxVelY) + // mVelY = mMaxVelY; + //else if (mCounter % 1 == 0) + // mVelY += mGravity; // Actualiza la posición del sprite mSprite->setPosX(getPosX()); - mSprite->setPosY(mPosY); - } - // Si no se puede mover: - // Comprobar si se está creando - else if (isBeingCreated() == true) - { - // Actualiza el valor de las variables - setStop(true); - setInvulnerable(true); - - // Todavia tiene tiempo en el contador - if (mCreationCounter > 0) - { - // Desplaza lentamente el globo hacia abajo y hacia un lado - if (mCreationCounter % 10 == 0) - { - ++mPosY; - mPosX += mVelX; - - // Actualiza la posición del sprite - mSprite->setPosX(getPosX()); - mSprite->setPosY(mPosY); - - // Actualiza la posición del circulo de colisión - shiftColliders(); - } - - // Hace visible el globo de forma intermitente - if (mCreationCounter > 100) - { - setVisible(mCreationCounter / 10 % 2 == 0); - } - else - { - setVisible(mCreationCounter / 5 % 2 == 0); - } - - --mCreationCounter; - } - // El contador ha llegado a cero - else - { - setBeingCreated(false); - setStop(false); - setVisible(true); - setInvulnerable(false); - } - } - // Comprobar si está detenido - else if (isStopped() == true) - { - // Si todavía está detenido, reduce el contador - if (mStoppedCounter > 0) - { - --mStoppedCounter; - } // Si el contador ha llegado a cero, ya no está detenido - else - { - setStop(false); - } + mSprite->setPosY(getPosY()); } } -// Pone a cero todos los valores del globo -void Balloon::erase() +// Deshabilita el globo y pone a cero todos los valores +void Balloon::disable() { - init(0, 0, NO_KIND, 0, 0, nullptr, nullptr); + mEnabled = false; + + mPosX = 0.0f; + mPosY = 0.0f; + mWidth = 0; + mHeight = 0; + mVelX = 0.0f; + mVelY = 0.0f; + mGravity = 0.0f; + mDefaultVelY = 0.0f; + mMaxVelY = 0.0f; + mBeingCreated = false; + mBlinking = false; + mInvulnerable = false; + mPopping = false; + mStopped = false; + mVisible = false; + mCollider.x = 0; + mCollider.y = 0; + mCollider.r = 0; + mCreationCounter = 0; + mCreationCounterIni = 0; + mScore = 0; + mStoppedCounter = 0; + mTimeToLive = 0; + mKind = 0; + mMenace = 0; } // Explosiona el globo @@ -458,64 +583,124 @@ void Balloon::pop() // Actualiza al globo a su posicion, animación y controla los contadores void Balloon::update() { - move(); - setAnimation(); - shiftColliders(); - bounceUpdate(); + if (mEnabled) + { + move(); + updateAnimation(); + updateColliders(); + updateState(); + updateBounce(); + mCounter++; + } +} + +// Actualiza los estados del globo +void Balloon::updateState() +{ + // Si está explotando if (isPopping()) { setInvulnerable(true); setStop(true); - if (mTimeToLive > 0) + if (mSprite->isCompleted(BALLOON_POP_ANIMATION)) { - --mTimeToLive; + mSprite->setCompleted(BALLOON_POP_ANIMATION, false); + mTimeToLive = 0; + disable(); } + else if (mTimeToLive > 0) + mTimeToLive--; + else + disable(); + } + + // Si se está creando + if (isBeingCreated()) + { + // Actualiza el valor de las variables + setStop(true); + setInvulnerable(true); + + // Todavia tiene tiempo en el contador + if (mCreationCounter > 0) + { + // Desplaza lentamente el globo hacia abajo y hacia un lado + if (mCreationCounter % 10 == 0) + { + mPosY++; + mPosX += mVelX; + + // Comprueba no se salga por los laterales + if ((mPosX < PLAY_AREA_LEFT) || (mPosX > (PLAY_AREA_RIGHT - mWidth))) + { + // Corrige y cambia el sentido de la velocidad + mPosX -= mVelX; + mVelX = -mVelX; + } + + // Actualiza la posición del sprite + mSprite->setPosX(getPosX()); + mSprite->setPosY(getPosY()); + + // Actualiza la posición del circulo de colisión + updateColliders(); + } + + // Hace visible el globo de forma intermitente + //if (mCreationCounter > 100) + // setVisible(mCreationCounter / 10 % 2 == 0); + //else + // setVisible(mCreationCounter / 5 % 2 == 0); + + mCreationCounter--; + } + // El contador ha llegado a cero else { - erase(); + setBeingCreated(false); + setStop(false); + setVisible(true); + setInvulnerable(false); } } + // Solo comprueba el estado detenido cuando no se está creando + else if (isStopped()) + { + // Si está detenido, reduce el contador + if (mStoppedCounter > 0) + mStoppedCounter--; + // Si el contador ha llegado a cero, ya no está detenido + else if (!isPopping()) // Quitarles el estado "detenido" si no estan explosionandoxq + setStop(false); + } } // Establece la animación correspondiente al estado -void Balloon::setAnimation() +void Balloon::updateAnimation() { // Establece el frame de animación if (isPopping()) - { mSprite->animate(BALLOON_POP_ANIMATION); - } else if (isBeingCreated()) - { mSprite->animate(BALLOON_BORN_ANIMATION); - } else - { mSprite->animate(BALLOON_MOVING_ANIMATION); - } } -// Comprueba si el globo tiene algun tipo asignado -bool Balloon::isActive() +// Comprueba si el globo está habilitado +bool Balloon::isEnabled() { - if (mKind == NO_KIND) - { - return false; - } - else - { - return true; - } + return mEnabled; } // Obtiene del valor de la variable -int Balloon::getPosX() +float Balloon::getPosX() { - return int(mPosX); + return mPosX; } // Obtiene del valor de la variable -int Balloon::getPosY() +float Balloon::getPosY() { return mPosY; } @@ -544,12 +729,29 @@ void Balloon::setVelY(float velY) mVelY = velY; } +// Establece el valor de la variable +void Balloon::setSpeed(float speed) +{ + mSpeed = speed; +} + // Obtiene del valor de la variable int Balloon::getKind() { return mKind; } +// Obtiene la clase a la que pertenece el globo +Uint8 Balloon::getClass() +{ + if ((mKind >= BALLOON_1) && (mKind <= BALLOON_4)) + return BALLOON_CLASS; + else if ((mKind >= HEXAGON_1) && (mKind <= HEXAGON_4)) + return HEXAGON_CLASS; + else + return 0; +} + // Establece el valor de la variable void Balloon::setStop(bool state) { @@ -653,13 +855,13 @@ Uint16 Balloon::getScore() } // Obtiene el circulo de colisión -Circle &Balloon::getCollider() +circle_t &Balloon::getCollider() { return mCollider; } // Alinea el circulo de colisión con la posición del objeto globo -void Balloon::shiftColliders() +void Balloon::updateColliders() { // Align collider to center of balloon mCollider.x = Uint16(mPosX + mCollider.r); @@ -669,7 +871,10 @@ void Balloon::shiftColliders() // Obtiene le valor de la variable Uint8 Balloon::getMenace() { - return mMenace; + if (isEnabled()) + return mMenace; + else + return 0; } void Balloon::bounceStart() @@ -686,14 +891,14 @@ void Balloon::bounceStop() { mBouncing.enabled = false; mBouncing.counter = 0; - mBouncing.zoomW = 1; - mBouncing.zoomH = 1; + mBouncing.zoomW = 1.0f; + mBouncing.zoomH = 1.0f; mSprite->setZoomW(mBouncing.zoomW); mSprite->setZoomH(mBouncing.zoomH); - mBouncing.despX = 0; - mBouncing.despY = 0; + mBouncing.despX = 0.0f; + mBouncing.despY = 0.0f; } -void Balloon::bounceUpdate() +void Balloon::updateBounce() { if (mBouncing.enabled) { diff --git a/source/balloon.h b/source/balloon.h index b46531d..de6db8a 100644 --- a/source/balloon.h +++ b/source/balloon.h @@ -11,33 +11,34 @@ class Balloon { private: - float mPosX; // Posición en el eje X - int mPosY; // Posición en el eje Y - - Uint8 mWidth; // Ancho - Uint8 mHeight; // Alto - - float mVelX; // Velocidad en el eje X. Cantidad de pixeles a desplazarse - float mVelY; // Velocidad en el eje Y. Cantidad de pixeles a desplazarse - float mGravity; // Aceleración en el eje Y. Modifica la velocidad - float mDefaultVelY; // Velocidad inicial que tienen al rebotar contra el suelo - int mMaxVelY; // Máxima velocidad que puede alcanzar el objeto en el eje Y - + float mPosX; // Posición en el eje X + float mPosY; // Posición en el eje Y + Uint8 mWidth; // Ancho + Uint8 mHeight; // Alto + float mVelX; // Velocidad en el eje X. Cantidad de pixeles a desplazarse + float mVelY; // Velocidad en el eje Y. Cantidad de pixeles a desplazarse + float mGravity; // Aceleración en el eje Y. Modifica la velocidad + float mDefaultVelY; // Velocidad inicial que tienen al rebotar contra el suelo + float mMaxVelY; // Máxima velocidad que puede alcanzar el objeto en el eje Y AnimatedSprite *mSprite; // Sprite del objeto globo bool mBeingCreated; // Indica si el globo se está creando bool mBlinking; // Indica si el globo está intermitente + bool mEnabled; // Indica si el globo esta activo bool mInvulnerable; // Indica si el globo es invulnerable bool mPopping; // Indica si el globo está explotando bool mStopped; // Indica si el globo está parado bool mVisible; // Indica si el globo es visible - Circle mCollider; // Circulo de colisión del objeto + circle_t mCollider; // Circulo de colisión del objeto Uint16 mCreationCounter; // Temporizador para controlar el estado "creandose" Uint16 mCreationCounterIni; // Valor inicial para el temporizador para controlar el estado "creandose" Uint16 mScore; // Puntos que da el globo al ser destruido Uint16 mStoppedCounter; // Contador para controlar el estado "parado" Uint16 mTimeToLive; // Indica el tiempo de vida que le queda al globo Uint8 mKind; // Tipo de globo - Uint8 mMenace; // Cantidad de amenaza que genera el globo + Uint8 mMenace; // Cantidad de amenaza que genera el globo + Uint32 mCounter; // Contador interno + float mTravelY; // Distancia que ha de recorrer el globo en el eje Y antes de que se le aplique la gravedad + float mSpeed; // Velocidad a la que se mueven los globos struct bouncing // Estructura para las variables para el efecto de los rebotes { @@ -53,13 +54,17 @@ private: float w[MAX_BOUNCE] = {1.10f, 1.05f, 1.00f, 0.95f, 0.90f, 0.95f, 1.00f, 1.02f, 1.05f, 1.02f}; float h[MAX_BOUNCE] = {0.90f, 0.95f, 1.00f, 1.05f, 1.10f, 1.05f, 1.00f, 0.98f, 0.95f, 0.98f}; }; - bouncing mBouncing; - void shiftColliders(); // Alinea el circulo de colisión con la posición del objeto globo - void bounceStart(); // Activa el efecto - void bounceStop(); // Detiene el efecto - void bounceUpdate(); // Aplica el efecto + void updateColliders(); // Alinea el circulo de colisión con la posición del objeto globo + void bounceStart(); // Activa el efecto + void bounceStop(); // Detiene el efecto + void updateBounce(); // Aplica el efecto + void updateState(); // Actualiza los estados del globo + void updateAnimation(); // Establece la animación correspondiente + void setBeingCreated(bool state); // Establece el valor de la variable + void setTimeToLive(Uint16 time); // Establece el valor de la variable + Uint16 getTimeToLive(); // Obtiene del valor de la variable public: // Constructor @@ -69,7 +74,7 @@ public: ~Balloon(); // Inicializador - void init(float x, int y, Uint8 kind, float velx, Uint16 creationtimer, LTexture *texture, SDL_Renderer *renderer); + void init(float x, float y, Uint8 kind, float velx, float speed, Uint16 creationtimer, LTexture *texture, SDL_Renderer *renderer); // Centra el globo en la posición X void allignTo(int x); @@ -80,8 +85,8 @@ public: // Actualiza la posición y estados del globo void move(); - // Pone a cero todos los valores del globo - void erase(); + // Deshabilita el globo y pone a cero todos los valores + void disable(); // Explosiona el globo void pop(); @@ -89,17 +94,14 @@ public: // Actualiza al globo a su posicion, animación y controla los contadores void update(); - // Establece la animación correspondiente - void setAnimation(); - - // Comprueba si el globo tiene algun tipo asignado - bool isActive(); + // Comprueba si el globo está habilitado + bool isEnabled(); // Obtiene del valor de la variable - int getPosX(); + float getPosX(); // Obtiene del valor de la variable - int getPosY(); + float getPosY(); // Obtiene del valor de la variable float getVelY(); @@ -113,9 +115,15 @@ public: // Establece el valor de la variable void setVelY(float velY); + // Establece el valor de la variable + void setSpeed(float speed); + // Obtiene del valor de la variable int getKind(); + // Obtiene la clase a la que pertenece el globo + Uint8 getClass(); + // Establece el valor de la variable void setStop(bool state); @@ -140,9 +148,6 @@ public: // Obtiene del valor de la variable bool isInvulnerable(); - // Establece el valor de la variable - void setBeingCreated(bool state); - // Obtiene del valor de la variable bool isBeingCreated(); @@ -152,12 +157,6 @@ public: // Obtiene del valor de la variable bool isPopping(); - // Establece el valor de la variable - void setTimeToLive(Uint16 time); - - // Obtiene del valor de la variable - Uint16 getTimeToLive(); - // Establece el valor de la variable void setStoppedTimer(Uint16 time); @@ -168,7 +167,7 @@ public: Uint16 getScore(); // Obtiene el circulo de colisión - Circle &getCollider(); + circle_t &getCollider(); // Obtiene le valor de la variable Uint8 getMenace(); diff --git a/source/bullet.cpp b/source/bullet.cpp index f6b078d..f2dbf6e 100644 --- a/source/bullet.cpp +++ b/source/bullet.cpp @@ -200,7 +200,7 @@ int Bullet::getKind() } // Obtiene el circulo de colisión -Circle &Bullet::getCollider() +circle_t &Bullet::getCollider() { return mCollider; } diff --git a/source/bullet.h b/source/bullet.h index 26c5d24..add26ce 100644 --- a/source/bullet.h +++ b/source/bullet.h @@ -49,7 +49,7 @@ public: int getKind(); // Obtiene el circulo de colisión - Circle &getCollider(); + circle_t &getCollider(); private: // Posición X/Y del objeto @@ -71,7 +71,7 @@ private: Sprite *mSprite; // Balloon's collision circle - Circle mCollider; + circle_t mCollider; // Alinea el circulo de colisión con el objeto void shiftColliders(); diff --git a/source/const.h b/source/const.h index c547b54..5c38e87 100644 --- a/source/const.h +++ b/source/const.h @@ -75,7 +75,7 @@ const int VIEW_HEIGHT = SCREEN_HEIGHT * 3; // Cantidad de enteros a escribir en los ficheros de datos #define TOTAL_SCORE_DATA 3 -#define TOTAL_DEMO_DATA 2000 +#define TOTAL_DEMO_DATA 2000 // Zona de juego const int PLAY_AREA_TOP = (0 * BLOCK); @@ -85,6 +85,8 @@ const int PLAY_AREA_RIGHT = SCREEN_WIDTH - (0 * BLOCK); const int PLAY_AREA_WIDTH = PLAY_AREA_RIGHT - PLAY_AREA_LEFT; const int PLAY_AREA_HEIGHT = PLAY_AREA_BOTTOM - PLAY_AREA_TOP; const int PLAY_AREA_CENTER_X = PLAY_AREA_LEFT + (PLAY_AREA_WIDTH / 2); +const int PLAY_AREA_CENTER_FIRST_QUARTER_X = (PLAY_AREA_WIDTH / 4); +const int PLAY_AREA_CENTER_THIRD_QUARTER_X = (PLAY_AREA_WIDTH / 4) * 3; const int PLAY_AREA_CENTER_Y = PLAY_AREA_TOP + (PLAY_AREA_HEIGHT / 2); const int PLAY_AREA_FIRST_QUARTER_Y = PLAY_AREA_HEIGHT / 4; const int PLAY_AREA_THIRD_QUARTER_Y = (PLAY_AREA_HEIGHT / 4) * 3; @@ -127,12 +129,6 @@ const int SCREEN_THIRD_QUARTER_Y = (SCREEN_HEIGHT / 4) * 3; #define PLAYER_ANIMATION_BODY_FIRING_RIGHT 3 #define PLAYER_ANIMATION_BODY_WALKING_STOP 4 #define PLAYER_ANIMATION_BODY_FIRING_UP 5 -#define PLAYER_ANIMATION_BODY_WALKING_LEFT_EXTRA_HIT 6 -#define PLAYER_ANIMATION_BODY_FIRING_LEFT_EXTRA_HIT 7 -#define PLAYER_ANIMATION_BODY_WALKING_RIGHT_EXTRA_HIT 8 -#define PLAYER_ANIMATION_BODY_FIRING_RIGHT_EXTRA_HIT 9 -#define PLAYER_ANIMATION_BODY_WALKING_STOP_EXTRA_HIT 10 -#define PLAYER_ANIMATION_BODY_FIRING_UP_EXTRA_HIT 11 // Variables del jugador #define PLAYER_INVULNERABLE_COUNTER 200 @@ -208,6 +204,15 @@ const int MULTIPLIER_NUMBER_Y = SCREEN_HEIGHT - (2 * BLOCK) + 2; #define BALLOON_2 2 #define BALLOON_3 3 #define BALLOON_4 4 +#define HEXAGON_1 5 +#define HEXAGON_2 6 +#define HEXAGON_3 7 +#define HEXAGON_4 8 +#define POWER_BALL 9 + +// Clases de globo +#define BALLOON_CLASS 0 +#define HEXAGON_CLASS 1 // Velocidad del globo #define BALLOON_VELX_POSITIVE 0.7f @@ -219,7 +224,20 @@ const int MULTIPLIER_NUMBER_Y = SCREEN_HEIGHT - (2 * BLOCK) + 2; #define BALLOON_BORN_ANIMATION 2 // Cantidad posible de globos -#define MAX_BALLOONS 75 +#define MAX_BALLOONS 100 + +// Velocidades a las que se mueven los enemigos +#define BALLOON_SPEED_1 0.60f +#define BALLOON_SPEED_2 0.70f +#define BALLOON_SPEED_3 0.80f +#define BALLOON_SPEED_4 0.90f +#define BALLOON_SPEED_5 1.00f + +// Tamaño de los globos +#define BALLOON_SIZE_1 8 +#define BALLOON_SIZE_2 13 +#define BALLOON_SIZE_3 21 +#define BALLOON_SIZE_4 37 // Tipos de bala #define BULLET_UP 1 @@ -236,6 +254,7 @@ const int MULTIPLIER_NUMBER_Y = SCREEN_HEIGHT - (2 * BLOCK) + 2; #define ITEM_CLOCK 4 #define ITEM_TNT 5 #define ITEM_COFFEE 6 +#define ITEM_POWER_BALL 7 // Cantidad de objetos simultaneos #define MAX_ITEMS 5 @@ -280,5 +299,6 @@ const int MULTIPLIER_NUMBER_Y = SCREEN_HEIGHT - (2 * BLOCK) + 2; #define STAGE_COUNTER 200 #define INSTRUCTIONS_COUNTER 600 #define DEATH_COUNTER 350 +#define SHAKE_COUNTER 10 #endif \ No newline at end of file diff --git a/source/gamedirector.cpp b/source/gamedirector.cpp index 3dfa035..00c6c73 100644 --- a/source/gamedirector.cpp +++ b/source/gamedirector.cpp @@ -98,14 +98,20 @@ GameDirector::~GameDirector() // Inicia las variables necesarias para arrancar el programa void GameDirector::initProg() { + // Debug mode + mDebug.enabled = false; + mDebug.enemySet = 0; + mDebug.gradR = mDebug.gradG = mDebug.gradB = 0; + // Variables mProg.quit = false; - mProg.debug = false; mProg.ticks = 0; mProg.ticksSpeed = 15; mProg.section = PROG_SECTION_LOGO; initSin(); initPaths(); + initEnemyFormations(); + initEnemyPools(); mOptions.displayCoffeeDrops = false; @@ -182,6 +188,7 @@ void GameDirector::initProg() mMenu.gameOver->setSelectorColor(0x54, 0x6e, 0x7a, 0); mMenu.gameOver->setSelectorTextColor(0xFF, 0xFF, 0xFF); mMenu.gameOver->setSelectorTextColor(0xFF, 0xF1, 0x76); + mMenu.gameOver->setSelectorTextColor(0xFF, 0x7A, 0x00); mMenu.gameOver->centerMenu(SCREEN_CENTER_X); mMenu.gameOver->centerMenuElements(); @@ -373,6 +380,7 @@ void GameDirector::initSin() // Inicializa las variables que contienen puntos de ruta para mover objetos void GameDirector::initPaths() { + // Letrero de STAGE # int firstPart = STAGE_COUNTER / 4; // 50 int secondPart = firstPart * 3; // 150 int centerPoint = PLAY_AREA_CENTER_Y - (BLOCK * 2); @@ -381,11 +389,39 @@ void GameDirector::initPaths() for (int i = 0; i < STAGE_COUNTER; i++) { if (i < firstPart) - mGame.stagePath[i] = (mSin[(int)((i * 1.8f) + 90)] * (distance) + centerPoint); + mGame.stageBitmapPath[i] = (mSin[(int)((i * 1.8f) + 90)] * (distance) + centerPoint); else if (i < secondPart) - mGame.stagePath[i] = (int)centerPoint; + mGame.stageBitmapPath[i] = (int)centerPoint; else - mGame.stagePath[i] = (mSin[(int)(((i - 149) * 1.8f) + 90)] * (centerPoint + 17) - 17); + mGame.stageBitmapPath[i] = (mSin[(int)(((i - 149) * 1.8f) + 90)] * (centerPoint + 17) - 17); + } + + float start1 = PLAY_AREA_LEFT - 130; + float finish1 = PLAY_AREA_CENTER_X - 55; + + float start2 = finish1; + float finish2 = PLAY_AREA_RIGHT + 20; + + float distance1 = finish1 - start1; + float distance2 = finish2 - start2; + + // Letrero de GetReady + for (int i = 0; i < STAGE_COUNTER; i++) + { + if (i < firstPart) + { + mGame.getReadyBitmapPath[i] = mSin[(int)(i * 1.8f)]; + mGame.getReadyBitmapPath[i] *= distance1; + mGame.getReadyBitmapPath[i] -= 130; + } + else if (i < secondPart) + mGame.getReadyBitmapPath[i] = (int)finish1; + else + { + mGame.getReadyBitmapPath[i] = mSin[(int)((i - 150) * 1.8f)]; + mGame.getReadyBitmapPath[i] *= distance2; + mGame.getReadyBitmapPath[i] += finish1; + } } } @@ -813,9 +849,11 @@ void GameDirector::initGame() mGame.clouds1b = new MovingSprite(); mGame.clouds2a = new MovingSprite(); mGame.clouds2b = new MovingSprite(); - mGame.getReadyBitmap = new SmartSprite(); + mGame.getReadyBitmap = new Sprite(); mGame.gradient = new Sprite(); mGame.grass = new Sprite(); + mGame.scoreBoard = new Sprite(); + mGame.powerMeter = new Sprite(); // Carga los recursos loadMediaGame(); @@ -829,22 +867,29 @@ void GameDirector::resetGame() { // Inicializa las variables mGame.section = GAME_SECTION_PLAY; - mGame.menaceLevelCurrent = 0; - mGame.menaceLevelThreshold = 0; + mGame.menaceCurrent = 0; + mGame.menaceThreshold = 0; mGame.score = 0; mGame.hiScoreAchieved = false; - mGame.stage = 1; - mGame.stageCounter = STAGE_COUNTER; + mGame.currentStage = 0; + mGame.stageBitmapCounter = STAGE_COUNTER; mGame.deathCounter = DEATH_COUNTER; mGame.explosionTime = false; mGame.remainingExplosions = REMAINING_EXPLOSIONS; mGame.remainingExplosionsCounter = REMAINING_EXPLOSIONS_COUNTER; mGame.timeStopped = false; mGame.timeStoppedCounter = TIME_STOPPED_COUNTER; - mProg.debug = false; mGame.counter = 0; + mGame.balloonsPopped = 0; + mGame.lastEnemyDeploy = 0; + mGame.enemyDeployCounter = 0; + mGame.enemySpeed = BALLOON_SPEED_1; + mGame.effect.flash = false; + mGame.effect.shake = false; + mGame.effect.shakeCounter = SHAKE_COUNTER; + initGameStages(); - // Fondo animado + // Sprites mGame.clouds1a->init(0, 0, 256, 52, -0.4f, 0.0f, 0.0f, 0.0f, mTexture[TEXTURE_GAME_BG].texture, mRenderer); mGame.clouds1a->setSpriteClip(256, 0, 256, 52); @@ -860,6 +905,12 @@ void GameDirector::resetGame() mGame.grass->init(0, 85, SCREEN_WIDTH, 6, mTexture[TEXTURE_GAME_BG].texture, mRenderer); mGame.grass->setPosY(154); + mGame.scoreBoard->init(0, 160, SCREEN_WIDTH, 32, mTexture[TEXTURE_GAME_BG].texture, mRenderer); + mGame.scoreBoard->setSpriteClip(0, 160, 256, 32); + + mGame.powerMeter->init(PLAY_AREA_CENTER_THIRD_QUARTER_X - 20, HISCORE_NUMBER_Y + 4, 40, 8, mTexture[TEXTURE_GAME_BG].texture, mRenderer); + mGame.powerMeter->setSpriteClip(256, 192 - 8, 40, 8); + // Objeto jugador mGame.player->init(PLAY_AREA_CENTER_X - 11, PLAY_AREA_BOTTOM - 24, mTexture[TEXTURE_PLAYER_LEGS].texture, mTexture[TEXTURE_PLAYER_BODY].texture, mRenderer); @@ -867,11 +918,11 @@ void GameDirector::resetGame() resetBalloons(); // Crea objetos globo y los centra en el area de juego - mGame.balloon[0]->init(0, BLOCK, BALLOON_4, BALLOON_VELX_POSITIVE, 100, mTexture[TEXTURE_BALLOON].texture, mRenderer); - mGame.balloon[0]->allignTo(PLAY_AREA_CENTER_X); + //mGame.balloon[0]->init(0, -BLOCK, POWER_BALL, BALLOON_VELX_POSITIVE, mGame.enemySpeed, 300, mTexture[TEXTURE_BALLOON].texture, mRenderer); + //mGame.balloon[0]->allignTo(PLAY_AREA_CENTER_X); // Con los globos creados, calcula el nivel de amenaza - setMenaceLevel(); + setMenace(); // Establece a cero todos los valores del vector de objetos bala resetBullets(); @@ -882,21 +933,9 @@ void GameDirector::resetGame() // Establece a cero todos los valores del vector de objetos SmafrtSprite resetSmartSprites(); - // Inicializa el bitmap de GetReady! - mGame.getReadyBitmap->init(mTexture[TEXTURE_GAME_TEXT].texture, mRenderer); - mGame.getReadyBitmap->setPosX(-113); - mGame.getReadyBitmap->setPosY(PLAY_AREA_CENTER_Y - 10); - mGame.getReadyBitmap->setWidth(109); - mGame.getReadyBitmap->setHeight(20); - mGame.getReadyBitmap->setVelX(2.0f); - mGame.getReadyBitmap->setVelY(0.0f); - mGame.getReadyBitmap->setAccelX(0.00f); - mGame.getReadyBitmap->setAccelY(0.0f); + // Inicializa el bitmap de GetLeady! + mGame.getReadyBitmap->init(0, PLAY_AREA_CENTER_Y - 10, 109, 20, mTexture[TEXTURE_GAME_TEXT].texture, mRenderer); mGame.getReadyBitmap->setSpriteClip(0, 0, 109, 20); - mGame.getReadyBitmap->setEnabled(!mDemo.enabled); - mGame.getReadyBitmap->setEnabledTimer(0); - mGame.getReadyBitmap->setDestX(PLAY_AREA_RIGHT); - mGame.getReadyBitmap->setDestY(PLAY_AREA_CENTER_Y - 10); // Inicializa el bitmap de 1000 puntos mGame._1000Bitmap->init(mTexture[TEXTURE_GAME_TEXT].texture, mRenderer); @@ -949,6 +988,10 @@ void GameDirector::resetGame() // Los fondos mGame.background->init(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, mTexture[TEXTURE_GAME_BG].texture, mRenderer); mGame.gradient->init(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, mTexture[TEXTURE_GAME_BG].texture, mRenderer); + mGame.gradientRect[0] = {0, 192, SCREEN_WIDTH, SCREEN_HEIGHT}; + mGame.gradientRect[1] = {256, 192, SCREEN_WIDTH, SCREEN_HEIGHT}; + mGame.gradientRect[2] = {0, 384, SCREEN_WIDTH, SCREEN_HEIGHT}; + mGame.gradientRect[3] = {256, 384, SCREEN_WIDTH, SCREEN_HEIGHT}; } // Carga los recursos necesarios para la sección 'Game' @@ -1059,6 +1102,8 @@ void GameDirector::quitGame() delete mGame.getReadyBitmap; delete mGame.gradient; delete mGame.grass; + delete mGame.scoreBoard; + delete mGame.powerMeter; mTexture[TEXTURE_PLAYER_LEGS].texture = nullptr; mTexture[TEXTURE_PLAYER_BODY].texture = nullptr; mTexture[TEXTURE_PLAYER_DEATH].texture = nullptr; @@ -1079,6 +1124,8 @@ void GameDirector::quitGame() mGame.getReadyBitmap = nullptr; mGame.gradient = nullptr; mGame.grass = nullptr; + mGame.scoreBoard = nullptr; + mGame.powerMeter = nullptr; } // Crea el indice de ficheros @@ -1358,7 +1405,7 @@ bool GameDirector::loadDemoFile() mDemo.keys.fireLeft = 0; mDemo.keys.fireRight = 0; mDemo.dataFile[i] = mDemo.keys; - SDL_RWwrite(file, &mDemo.dataFile[i], sizeof(DemoKeys), 1); + SDL_RWwrite(file, &mDemo.dataFile[i], sizeof(demoKeys_t), 1); } // Cerramos el fichero @@ -1376,7 +1423,7 @@ bool GameDirector::loadDemoFile() // Cargamos los datos printf("Reading file %s\n", filename.c_str()); for (int i = 0; i < TOTAL_DEMO_DATA; ++i) - SDL_RWread(file, &mDemo.dataFile[i], sizeof(DemoKeys), 1); + SDL_RWread(file, &mDemo.dataFile[i], sizeof(demoKeys_t), 1); // Cierra el fichero SDL_RWclose(file); @@ -1454,7 +1501,7 @@ bool GameDirector::saveDemoFile() // Guardamos los datos for (int i = 0; i < TOTAL_DEMO_DATA; ++i) { - SDL_RWwrite(file, &mDemo.dataFile[i], sizeof(DemoKeys), 1); + SDL_RWwrite(file, &mDemo.dataFile[i], sizeof(demoKeys_t), 1); } printf("Writing file %s\n", filename.c_str()); @@ -1494,6 +1541,745 @@ void GameDirector::setExecutablePath(std::string path) mProg.executablePath = path.substr(0, path.find_last_of("\\/")); } +// Inicializa las formaciones enemigas +void GameDirector::initEnemyFormations() +{ + const int y4 = (PLAY_AREA_TOP - BLOCK); + const int x4_0 = PLAY_AREA_LEFT; + //const int x4_50 = PLAY_AREA_CENTER_X - (BALLOON_SIZE_4 / 2); + const int x4_100 = (PLAY_AREA_RIGHT)-BALLOON_SIZE_4; + + const int y3 = (PLAY_AREA_TOP - BLOCK); + const int x3_0 = PLAY_AREA_LEFT; + //const int x3_50 = PLAY_AREA_CENTER_X - (BALLOON_SIZE_3 / 2); + const int x3_100 = (PLAY_AREA_RIGHT)-BALLOON_SIZE_3; + + const int y2 = (PLAY_AREA_TOP - BLOCK); + const int x2_0 = PLAY_AREA_LEFT; + //const int x2_50 = PLAY_AREA_CENTER_X - (BALLOON_SIZE_2 / 2); + const int x2_100 = (PLAY_AREA_RIGHT)-BALLOON_SIZE_2; + + const int y1 = (PLAY_AREA_TOP - BLOCK); + const int x1_0 = PLAY_AREA_LEFT; + const int x1_50 = PLAY_AREA_CENTER_X - (BALLOON_SIZE_1 / 2); + const int x1_100 = (PLAY_AREA_RIGHT)-BALLOON_SIZE_1; + + const Uint16 creationTime = 300; + int incX = 0; + Uint8 incTime = 0; + Uint8 j = 0; + + // #00 - Dos enemigos BALLOON4 uno a cada extremo + j = 0; + mEnemyFormation[j].numberOfEnemies = 2; + incX = x4_100; + incTime = 0; + for (Uint8 i = 0; i < mEnemyFormation[j].numberOfEnemies; i++) + { + mEnemyFormation[j].init[i].x = x4_0 + (i * incX); + mEnemyFormation[j].init[i].y = y4; + mEnemyFormation[j].init[i].velX = BALLOON_VELX_NEGATIVE * (((i % 2) * 2) - 1); + mEnemyFormation[j].init[i].kind = BALLOON_4; + mEnemyFormation[j].init[i].creationCounter = creationTime + (incTime * i); + } + + // #01 - Dos enemigos BALLOON4 uno a cada cuarto. Ambos van hacia el centro + j = 1; + mEnemyFormation[j].numberOfEnemies = 2; + incX = PLAY_AREA_CENTER_X; + incTime = 0; + for (Uint8 i = 0; i < mEnemyFormation[j].numberOfEnemies; i++) + { + mEnemyFormation[j].init[i].x = PLAY_AREA_CENTER_FIRST_QUARTER_X - (BALLOON_SIZE_4 / 2) + (i * incX); + mEnemyFormation[j].init[i].y = y4; + mEnemyFormation[j].init[i].velX = BALLOON_VELX_NEGATIVE * (((i % 2) * 2) - 1); + mEnemyFormation[j].init[i].kind = BALLOON_4; + mEnemyFormation[j].init[i].creationCounter = creationTime + (incTime * i); + } + + // #02 - Cuatro enemigos BALLOON2 uno detras del otro. A la izquierda y hacia el centro + j = 2; + mEnemyFormation[j].numberOfEnemies = 4; + incX = BALLOON_SIZE_2 + 1; + incTime = 10; + for (Uint8 i = 0; i < mEnemyFormation[j].numberOfEnemies; i++) + { + mEnemyFormation[j].init[i].x = x2_0 + (i * incX); + mEnemyFormation[j].init[i].y = y2; + mEnemyFormation[j].init[i].velX = BALLOON_VELX_POSITIVE; + mEnemyFormation[j].init[i].kind = BALLOON_2; + mEnemyFormation[j].init[i].creationCounter = creationTime - (incTime * i); + } + + // #03 - Cuatro enemigos BALLOON2 uno detras del otro. A la derecha y hacia el centro + j = 3; + mEnemyFormation[j].numberOfEnemies = 4; + incX = BALLOON_SIZE_2 + 1; + incTime = 10; + for (Uint8 i = 0; i < mEnemyFormation[j].numberOfEnemies; i++) + { + mEnemyFormation[j].init[i].x = x2_100 - (i * incX); + mEnemyFormation[j].init[i].y = y2; + mEnemyFormation[j].init[i].velX = BALLOON_VELX_NEGATIVE; + mEnemyFormation[j].init[i].kind = BALLOON_2; + mEnemyFormation[j].init[i].creationCounter = creationTime - (incTime * i); + } + + // #04 - Tres enemigos BALLOON3. 0, 25, 50. Hacia la derecha + j = 4; + mEnemyFormation[j].numberOfEnemies = 3; + incX = BALLOON_SIZE_3 * 2; + incTime = 10; + for (Uint8 i = 0; i < mEnemyFormation[j].numberOfEnemies; i++) + { + mEnemyFormation[j].init[i].x = x3_0 + (i * incX); + mEnemyFormation[j].init[i].y = y3; + mEnemyFormation[j].init[i].velX = BALLOON_VELX_POSITIVE; + mEnemyFormation[j].init[i].kind = BALLOON_3; + mEnemyFormation[j].init[i].creationCounter = creationTime - (incTime * i); + } + + // #05 - Tres enemigos BALLOON3. 50, 75, 100. Hacia la izquierda + j = 5; + mEnemyFormation[j].numberOfEnemies = 3; + incX = BALLOON_SIZE_3 * 2; + incTime = 10; + for (Uint8 i = 0; i < mEnemyFormation[j].numberOfEnemies; i++) + { + mEnemyFormation[j].init[i].x = x3_100 - (i * incX); + mEnemyFormation[j].init[i].y = y3; + mEnemyFormation[j].init[i].velX = BALLOON_VELX_NEGATIVE; + mEnemyFormation[j].init[i].kind = BALLOON_3; + mEnemyFormation[j].init[i].creationCounter = creationTime - (incTime * i); + } + + // #06 - Tres enemigos BALLOON3. 0, 0, 0. Hacia la derecha + j = 6; + mEnemyFormation[j].numberOfEnemies = 3; + incX = BALLOON_SIZE_3 + 1; + incTime = 10; + for (Uint8 i = 0; i < mEnemyFormation[j].numberOfEnemies; i++) + { + mEnemyFormation[j].init[i].x = x3_0 + (i * incX); + mEnemyFormation[j].init[i].y = y3; + mEnemyFormation[j].init[i].velX = BALLOON_VELX_POSITIVE; + mEnemyFormation[j].init[i].kind = BALLOON_3; + mEnemyFormation[j].init[i].creationCounter = creationTime - (incTime * i); + } + + // #07 - Tres enemigos BALLOON3. 100, 100, 100. Hacia la izquierda + j = 7; + mEnemyFormation[j].numberOfEnemies = 3; + incX = BALLOON_SIZE_3 + 1; + incTime = 10; + for (Uint8 i = 0; i < mEnemyFormation[j].numberOfEnemies; i++) + { + mEnemyFormation[j].init[i].x = x3_100 - (i * incX); + mEnemyFormation[j].init[i].y = y3; + mEnemyFormation[j].init[i].velX = BALLOON_VELX_NEGATIVE; + mEnemyFormation[j].init[i].kind = BALLOON_3; + mEnemyFormation[j].init[i].creationCounter = creationTime - (incTime * i); + } + + // #08 - Seis enemigos BALLOON1. 0, 0, 0, 0, 0, 0. Hacia la derecha + j = 8; + mEnemyFormation[j].numberOfEnemies = 6; + incX = BALLOON_SIZE_1 + 1; + incTime = 10; + for (Uint8 i = 0; i < mEnemyFormation[j].numberOfEnemies; i++) + { + mEnemyFormation[j].init[i].x = x1_0 + (i * incX); + mEnemyFormation[j].init[i].y = 13; + mEnemyFormation[j].init[i].velX = BALLOON_VELX_POSITIVE; + mEnemyFormation[j].init[i].kind = BALLOON_1; + mEnemyFormation[j].init[i].creationCounter = creationTime - (incTime * i); + } + + // #09 - Seis enemigos BALLOON1. 100, 100, 100, 100, 100, 100. Hacia la izquierda + j = 9; + mEnemyFormation[j].numberOfEnemies = 6; + incX = BALLOON_SIZE_1 + 1; + incTime = 10; + for (Uint8 i = 0; i < mEnemyFormation[j].numberOfEnemies; i++) + { + mEnemyFormation[j].init[i].x = x1_100 - (i * incX); + mEnemyFormation[j].init[i].y = 13; + mEnemyFormation[j].init[i].velX = BALLOON_VELX_NEGATIVE; + mEnemyFormation[j].init[i].kind = BALLOON_1; + mEnemyFormation[j].init[i].creationCounter = creationTime - (incTime * i); + } + + // #10 - Tres enemigos BALLOON4 seguidos desde la izquierda + j = 10; + mEnemyFormation[j].numberOfEnemies = 3; + incX = BALLOON_SIZE_4 + 1; + incTime = 15; + for (Uint8 i = 0; i < mEnemyFormation[j].numberOfEnemies; i++) + { + mEnemyFormation[j].init[i].x = x4_0 + (i * incX); + mEnemyFormation[j].init[i].y = y4; + mEnemyFormation[j].init[i].velX = BALLOON_VELX_POSITIVE; + mEnemyFormation[j].init[i].kind = BALLOON_4; + mEnemyFormation[j].init[i].creationCounter = creationTime - (incTime * i); + } + + // #11 - Tres enemigos BALLOON4 seguidos desde la derecha + j = 11; + mEnemyFormation[j].numberOfEnemies = 3; + incX = BALLOON_SIZE_4 + 1; + incTime = 15; + for (Uint8 i = 0; i < mEnemyFormation[j].numberOfEnemies; i++) + { + mEnemyFormation[j].init[i].x = x4_100 - (i * incX); + mEnemyFormation[j].init[i].y = y4; + mEnemyFormation[j].init[i].velX = BALLOON_VELX_NEGATIVE; + mEnemyFormation[j].init[i].kind = BALLOON_4; + mEnemyFormation[j].init[i].creationCounter = creationTime - (incTime * i); + } + + // #12 - Seis enemigos BALLOON2 uno detras del otro. A la izquierda y hacia el centro + j = 12; + mEnemyFormation[j].numberOfEnemies = 6; + incX = BALLOON_SIZE_2 + 1; + incTime = 10; + for (Uint8 i = 0; i < mEnemyFormation[j].numberOfEnemies; i++) + { + mEnemyFormation[j].init[i].x = x2_0 + (i * incX); + mEnemyFormation[j].init[i].y = y2; + mEnemyFormation[j].init[i].velX = BALLOON_VELX_POSITIVE; + mEnemyFormation[j].init[i].kind = BALLOON_2; + mEnemyFormation[j].init[i].creationCounter = creationTime - (incTime * i); + } + + // #13 - Seis enemigos BALLOON2 uno detras del otro. A la derecha y hacia el centro + j = 13; + mEnemyFormation[j].numberOfEnemies = 6; + incX = BALLOON_SIZE_2 + 1; + incTime = 10; + for (Uint8 i = 0; i < mEnemyFormation[j].numberOfEnemies; i++) + { + mEnemyFormation[j].init[i].x = x2_100 - (i * incX); + mEnemyFormation[j].init[i].y = y2; + mEnemyFormation[j].init[i].velX = BALLOON_VELX_NEGATIVE; + mEnemyFormation[j].init[i].kind = BALLOON_2; + mEnemyFormation[j].init[i].creationCounter = creationTime - (incTime * i); + } + + // #14 - Cinco enemigos BALLOON3. Hacia la derecha. Separados + j = 14; + mEnemyFormation[j].numberOfEnemies = 5; + incX = BALLOON_SIZE_3 * 2; + incTime = 10; + for (Uint8 i = 0; i < mEnemyFormation[j].numberOfEnemies; i++) + { + mEnemyFormation[j].init[i].x = x3_0 + (i * incX); + mEnemyFormation[j].init[i].y = y3; + mEnemyFormation[j].init[i].velX = BALLOON_VELX_POSITIVE; + mEnemyFormation[j].init[i].kind = BALLOON_3; + mEnemyFormation[j].init[i].creationCounter = creationTime - (incTime * i); + } + + // #15 - Cinco enemigos BALLOON3. Hacia la izquierda. Separados + j = 15; + mEnemyFormation[j].numberOfEnemies = 5; + incX = BALLOON_SIZE_3 * 2; + incTime = 10; + for (Uint8 i = 0; i < mEnemyFormation[j].numberOfEnemies; i++) + { + mEnemyFormation[j].init[i].x = x3_100 - (i * incX); + mEnemyFormation[j].init[i].y = y3; + mEnemyFormation[j].init[i].velX = BALLOON_VELX_NEGATIVE; + mEnemyFormation[j].init[i].kind = BALLOON_3; + mEnemyFormation[j].init[i].creationCounter = creationTime - (incTime * i); + } + + // #16 - Cinco enemigos BALLOON3. Hacia la derecha. Juntos + j = 16; + mEnemyFormation[j].numberOfEnemies = 5; + incX = BALLOON_SIZE_3 + 1; + incTime = 10; + for (Uint8 i = 0; i < mEnemyFormation[j].numberOfEnemies; i++) + { + mEnemyFormation[j].init[i].x = x3_0 + (i * incX); + mEnemyFormation[j].init[i].y = y3; + mEnemyFormation[j].init[i].velX = BALLOON_VELX_POSITIVE; + mEnemyFormation[j].init[i].kind = BALLOON_3; + mEnemyFormation[j].init[i].creationCounter = creationTime - (incTime * i); + } + + // #17 - Cinco enemigos BALLOON3. Hacia la izquierda. Juntos + j = 17; + mEnemyFormation[j].numberOfEnemies = 5; + incX = BALLOON_SIZE_3 + 1; + incTime = 10; + for (Uint8 i = 0; i < mEnemyFormation[j].numberOfEnemies; i++) + { + mEnemyFormation[j].init[i].x = x3_100 - (i * incX); + mEnemyFormation[j].init[i].y = y3; + mEnemyFormation[j].init[i].velX = BALLOON_VELX_NEGATIVE; + mEnemyFormation[j].init[i].kind = BALLOON_3; + mEnemyFormation[j].init[i].creationCounter = creationTime - (incTime * i); + } + + // #18 - Doce enemigos BALLOON1. Hacia la derecha. Juntos + j = 18; + mEnemyFormation[j].numberOfEnemies = 12; + incX = BALLOON_SIZE_1 + 1; + incTime = 10; + for (Uint8 i = 0; i < mEnemyFormation[j].numberOfEnemies; i++) + { + mEnemyFormation[j].init[i].x = x1_0 + (i * incX); + mEnemyFormation[j].init[i].y = y1; + mEnemyFormation[j].init[i].velX = BALLOON_VELX_POSITIVE; + mEnemyFormation[j].init[i].kind = BALLOON_1; + mEnemyFormation[j].init[i].creationCounter = creationTime - (incTime * i); + } + + // #19 - Doce enemigos BALLOON1. Hacia la izquierda. Juntos + j = 19; + mEnemyFormation[j].numberOfEnemies = 12; + incX = BALLOON_SIZE_1 + 1; + incTime = 10; + for (Uint8 i = 0; i < mEnemyFormation[j].numberOfEnemies; i++) + { + mEnemyFormation[j].init[i].x = x1_100 - (i * incX); + mEnemyFormation[j].init[i].y = y1; + mEnemyFormation[j].init[i].velX = BALLOON_VELX_NEGATIVE; + mEnemyFormation[j].init[i].kind = BALLOON_1; + mEnemyFormation[j].init[i].creationCounter = creationTime - (incTime * i); + } + + // #20 - Dos enemigos BALLOON4 seguidos desde la izquierda/derecha. Simetricos + j = 20; + mEnemyFormation[j].numberOfEnemies = 4; + incX = BALLOON_SIZE_4 + 1; + incTime = 0; + for (Uint8 i = 0; i < mEnemyFormation[j].numberOfEnemies; i++) + { + Uint8 half = mEnemyFormation[j].numberOfEnemies / 2; + if (i < half) + { + mEnemyFormation[j].init[i].x = x4_0 + (i * incX); + mEnemyFormation[j].init[i].velX = BALLOON_VELX_POSITIVE; + } + else + { + mEnemyFormation[j].init[i].x = x4_100 - ((i - half) * incX); + mEnemyFormation[j].init[i].velX = BALLOON_VELX_NEGATIVE; + } + mEnemyFormation[j].init[i].y = y4; + mEnemyFormation[j].init[i].kind = BALLOON_4; + mEnemyFormation[j].init[i].creationCounter = creationTime + (incTime * i); + } + + // #21 - Diez enemigos BALLOON2 uno detras del otro. Izquierda/derecha. Simetricos + j = 21; + mEnemyFormation[j].numberOfEnemies = 10; + incX = BALLOON_SIZE_2 + 1; + incTime = 3; + for (Uint8 i = 0; i < mEnemyFormation[j].numberOfEnemies; i++) + { + Uint8 half = mEnemyFormation[j].numberOfEnemies / 2; + if (i < half) + { + mEnemyFormation[j].init[i].x = x2_0 + (i * incX); + mEnemyFormation[j].init[i].velX = BALLOON_VELX_POSITIVE; + mEnemyFormation[j].init[i].creationCounter = (creationTime) - (incTime * i); + } + else + { + mEnemyFormation[j].init[i].x = x2_100 - ((i - half) * incX); + mEnemyFormation[j].init[i].velX = BALLOON_VELX_NEGATIVE; + mEnemyFormation[j].init[i].creationCounter = (creationTime) - (incTime * (i - half)); + } + mEnemyFormation[j].init[i].y = y2; + mEnemyFormation[j].init[i].kind = BALLOON_2; + } + + // #22 - Diez enemigos BALLOON3. Hacia la derecha/izquierda. Separados. Simetricos + j = 22; + mEnemyFormation[j].numberOfEnemies = 10; + incX = BALLOON_SIZE_3 * 2; + incTime = 10; + for (Uint8 i = 0; i < mEnemyFormation[j].numberOfEnemies; i++) + { + Uint8 half = mEnemyFormation[j].numberOfEnemies / 2; + if (i < half) + { + mEnemyFormation[j].init[i].x = x3_0 + (i * incX); + mEnemyFormation[j].init[i].velX = BALLOON_VELX_POSITIVE; + mEnemyFormation[j].init[i].creationCounter = (creationTime) - (incTime * i); + } + else + { + mEnemyFormation[j].init[i].x = x3_100 - ((i - half) * incX); + mEnemyFormation[j].init[i].velX = BALLOON_VELX_NEGATIVE; + mEnemyFormation[j].init[i].creationCounter = (creationTime) - (incTime * (i - half)); + } + mEnemyFormation[j].init[i].y = y3; + mEnemyFormation[j].init[i].kind = BALLOON_3; + } + + // #23 - Diez enemigos BALLOON3. Hacia la derecha. Juntos. Simetricos + j = 23; + mEnemyFormation[j].numberOfEnemies = 10; + incX = BALLOON_SIZE_3 + 1; + incTime = 10; + for (Uint8 i = 0; i < mEnemyFormation[j].numberOfEnemies; i++) + { + Uint8 half = mEnemyFormation[j].numberOfEnemies / 2; + if (i < half) + { + mEnemyFormation[j].init[i].x = x3_0 + (i * incX); + mEnemyFormation[j].init[i].velX = BALLOON_VELX_POSITIVE; + mEnemyFormation[j].init[i].creationCounter = (creationTime) - (incTime * i); + } + else + { + mEnemyFormation[j].init[i].x = x3_100 - ((i - half) * incX); + mEnemyFormation[j].init[i].velX = BALLOON_VELX_NEGATIVE; + mEnemyFormation[j].init[i].creationCounter = (creationTime) - (incTime * (i - half)); + } + mEnemyFormation[j].init[i].y = y3; + mEnemyFormation[j].init[i].kind = BALLOON_3; + } + + // #24 - Treinta enemigos BALLOON1. Del centro hacia los extremos. Juntos. Simetricos + j = 24; + mEnemyFormation[j].numberOfEnemies = 30; + incX = 0; + incTime = 5; + for (Uint8 i = 0; i < mEnemyFormation[j].numberOfEnemies; i++) + { + Uint8 half = mEnemyFormation[j].numberOfEnemies / 2; + if (i < half) + { + mEnemyFormation[j].init[i].x = x1_50; + mEnemyFormation[j].init[i].velX = BALLOON_VELX_POSITIVE; + mEnemyFormation[j].init[i].creationCounter = (creationTime) + (incTime * i); + } + else + { + mEnemyFormation[j].init[i].x = x1_50; + mEnemyFormation[j].init[i].velX = BALLOON_VELX_NEGATIVE; + mEnemyFormation[j].init[i].creationCounter = (creationTime) + (incTime * (i - half)); + } + mEnemyFormation[j].init[i].y = y1; + mEnemyFormation[j].init[i].kind = BALLOON_1; + } + + // #25 - Treinta enemigos BALLOON1. Del centro hacia adentro. Juntos. Simetricos + j = 25; + mEnemyFormation[j].numberOfEnemies = 30; + incX = BALLOON_SIZE_1 + 1; + incTime = 5; + for (Uint8 i = 0; i < mEnemyFormation[j].numberOfEnemies; i++) + { + Uint8 half = mEnemyFormation[j].numberOfEnemies / 2; + if (i < half) + { + mEnemyFormation[j].init[i].x = x1_50 + 20; + mEnemyFormation[j].init[i].velX = BALLOON_VELX_NEGATIVE; + mEnemyFormation[j].init[i].creationCounter = (creationTime) - (incTime * i); + } + else + { + mEnemyFormation[j].init[i].x = x1_50 - 20; + mEnemyFormation[j].init[i].velX = BALLOON_VELX_POSITIVE; + mEnemyFormation[j].init[i].creationCounter = (creationTime) - (incTime * (i - half)); + } + mEnemyFormation[j].init[i].y = y1; + mEnemyFormation[j].init[i].kind = BALLOON_1; + } + + // Crea las mismas formaciones pero con hexagonos a partir de la posición 50 del vector + for (Uint8 k = 0; k < 50; k++) + { + mEnemyFormation[k + 50].numberOfEnemies = mEnemyFormation[k].numberOfEnemies; + for (Uint8 i = 0; i < mEnemyFormation[k + 50].numberOfEnemies; i++) + { + mEnemyFormation[k + 50].init[i].x = mEnemyFormation[k].init[i].x; + mEnemyFormation[k + 50].init[i].y = mEnemyFormation[k].init[i].y; + mEnemyFormation[k + 50].init[i].velX = mEnemyFormation[k].init[i].velX; + mEnemyFormation[k + 50].init[i].creationCounter = mEnemyFormation[k].init[i].creationCounter; + mEnemyFormation[k + 50].init[i].kind = mEnemyFormation[k].init[i].kind + 4; + } + } + + // TEST + mEnemyFormation[99].numberOfEnemies = 4; + + mEnemyFormation[99].init[0].x = 10; + mEnemyFormation[99].init[0].y = y1; + mEnemyFormation[99].init[0].velX = 0; + mEnemyFormation[99].init[0].kind = BALLOON_1; + mEnemyFormation[99].init[0].creationCounter = 200; + + mEnemyFormation[99].init[1].x = 50; + mEnemyFormation[99].init[1].y = y1; + mEnemyFormation[99].init[1].velX = 0; + mEnemyFormation[99].init[1].kind = BALLOON_2; + mEnemyFormation[99].init[1].creationCounter = 200; + + mEnemyFormation[99].init[2].x = 90; + mEnemyFormation[99].init[2].y = y1; + mEnemyFormation[99].init[2].velX = 0; + mEnemyFormation[99].init[2].kind = BALLOON_3; + mEnemyFormation[99].init[2].creationCounter = 200; + + mEnemyFormation[99].init[3].x = 140; + mEnemyFormation[99].init[3].y = y1; + mEnemyFormation[99].init[3].velX = 0; + mEnemyFormation[99].init[3].kind = BALLOON_4; + mEnemyFormation[99].init[3].creationCounter = 200; +} + +// Inicializa los conjuntos de formaciones +void GameDirector::initEnemyPools() +{ + // EnemyPool #0 + mEnemyPool[0].set[0] = &mEnemyFormation[0]; + mEnemyPool[0].set[1] = &mEnemyFormation[1]; + mEnemyPool[0].set[2] = &mEnemyFormation[2]; + mEnemyPool[0].set[3] = &mEnemyFormation[3]; + mEnemyPool[0].set[4] = &mEnemyFormation[4]; + mEnemyPool[0].set[5] = &mEnemyFormation[5]; + mEnemyPool[0].set[6] = &mEnemyFormation[6]; + mEnemyPool[0].set[7] = &mEnemyFormation[7]; + mEnemyPool[0].set[8] = &mEnemyFormation[8]; + mEnemyPool[0].set[9] = &mEnemyFormation[9]; + + // EnemyPool #1 + mEnemyPool[1].set[0] = &mEnemyFormation[10]; + mEnemyPool[1].set[1] = &mEnemyFormation[11]; + mEnemyPool[1].set[2] = &mEnemyFormation[12]; + mEnemyPool[1].set[3] = &mEnemyFormation[13]; + mEnemyPool[1].set[4] = &mEnemyFormation[14]; + mEnemyPool[1].set[5] = &mEnemyFormation[15]; + mEnemyPool[1].set[6] = &mEnemyFormation[16]; + mEnemyPool[1].set[7] = &mEnemyFormation[17]; + mEnemyPool[1].set[8] = &mEnemyFormation[18]; + mEnemyPool[1].set[9] = &mEnemyFormation[19]; + + // EnemyPool #2 + mEnemyPool[2].set[0] = &mEnemyFormation[0]; + mEnemyPool[2].set[1] = &mEnemyFormation[1]; + mEnemyPool[2].set[2] = &mEnemyFormation[2]; + mEnemyPool[2].set[3] = &mEnemyFormation[3]; + mEnemyPool[2].set[4] = &mEnemyFormation[4]; + mEnemyPool[2].set[5] = &mEnemyFormation[55]; + mEnemyPool[2].set[6] = &mEnemyFormation[56]; + mEnemyPool[2].set[7] = &mEnemyFormation[57]; + mEnemyPool[2].set[8] = &mEnemyFormation[58]; + mEnemyPool[2].set[9] = &mEnemyFormation[59]; + + // EnemyPool #3 + mEnemyPool[3].set[0] = &mEnemyFormation[50]; + mEnemyPool[3].set[1] = &mEnemyFormation[51]; + mEnemyPool[3].set[2] = &mEnemyFormation[52]; + mEnemyPool[3].set[3] = &mEnemyFormation[53]; + mEnemyPool[3].set[4] = &mEnemyFormation[54]; + mEnemyPool[3].set[5] = &mEnemyFormation[5]; + mEnemyPool[3].set[6] = &mEnemyFormation[6]; + mEnemyPool[3].set[7] = &mEnemyFormation[7]; + mEnemyPool[3].set[8] = &mEnemyFormation[8]; + mEnemyPool[3].set[9] = &mEnemyFormation[9]; + + // EnemyPool #4 + mEnemyPool[4].set[0] = &mEnemyFormation[60]; + mEnemyPool[4].set[1] = &mEnemyFormation[61]; + mEnemyPool[4].set[2] = &mEnemyFormation[62]; + mEnemyPool[4].set[3] = &mEnemyFormation[63]; + mEnemyPool[4].set[4] = &mEnemyFormation[64]; + mEnemyPool[4].set[5] = &mEnemyFormation[65]; + mEnemyPool[4].set[6] = &mEnemyFormation[66]; + mEnemyPool[4].set[7] = &mEnemyFormation[67]; + mEnemyPool[4].set[8] = &mEnemyFormation[68]; + mEnemyPool[4].set[9] = &mEnemyFormation[69]; + + // EnemyPool #5 + mEnemyPool[5].set[0] = &mEnemyFormation[10]; + mEnemyPool[5].set[1] = &mEnemyFormation[61]; + mEnemyPool[5].set[2] = &mEnemyFormation[12]; + mEnemyPool[5].set[3] = &mEnemyFormation[63]; + mEnemyPool[5].set[4] = &mEnemyFormation[14]; + mEnemyPool[5].set[5] = &mEnemyFormation[65]; + mEnemyPool[5].set[6] = &mEnemyFormation[16]; + mEnemyPool[5].set[7] = &mEnemyFormation[67]; + mEnemyPool[5].set[8] = &mEnemyFormation[18]; + mEnemyPool[5].set[9] = &mEnemyFormation[69]; + + // EnemyPool #6 + mEnemyPool[6].set[0] = &mEnemyFormation[60]; + mEnemyPool[6].set[1] = &mEnemyFormation[11]; + mEnemyPool[6].set[2] = &mEnemyFormation[62]; + mEnemyPool[6].set[3] = &mEnemyFormation[13]; + mEnemyPool[6].set[4] = &mEnemyFormation[64]; + mEnemyPool[6].set[5] = &mEnemyFormation[15]; + mEnemyPool[6].set[6] = &mEnemyFormation[66]; + mEnemyPool[6].set[7] = &mEnemyFormation[17]; + mEnemyPool[6].set[8] = &mEnemyFormation[68]; + mEnemyPool[6].set[9] = &mEnemyFormation[19]; + + // EnemyPool #7 + mEnemyPool[7].set[0] = &mEnemyFormation[20]; + mEnemyPool[7].set[1] = &mEnemyFormation[21]; + mEnemyPool[7].set[2] = &mEnemyFormation[22]; + mEnemyPool[7].set[3] = &mEnemyFormation[23]; + mEnemyPool[7].set[4] = &mEnemyFormation[24]; + mEnemyPool[7].set[5] = &mEnemyFormation[65]; + mEnemyPool[7].set[6] = &mEnemyFormation[66]; + mEnemyPool[7].set[7] = &mEnemyFormation[67]; + mEnemyPool[7].set[8] = &mEnemyFormation[68]; + mEnemyPool[7].set[9] = &mEnemyFormation[69]; + + // EnemyPool #8 + mEnemyPool[8].set[0] = &mEnemyFormation[70]; + mEnemyPool[8].set[1] = &mEnemyFormation[71]; + mEnemyPool[8].set[2] = &mEnemyFormation[72]; + mEnemyPool[8].set[3] = &mEnemyFormation[73]; + mEnemyPool[8].set[4] = &mEnemyFormation[74]; + mEnemyPool[8].set[5] = &mEnemyFormation[15]; + mEnemyPool[8].set[6] = &mEnemyFormation[16]; + mEnemyPool[8].set[7] = &mEnemyFormation[17]; + mEnemyPool[8].set[8] = &mEnemyFormation[18]; + mEnemyPool[8].set[9] = &mEnemyFormation[19]; + + // EnemyPool #9 + mEnemyPool[9].set[0] = &mEnemyFormation[20]; + mEnemyPool[9].set[1] = &mEnemyFormation[21]; + mEnemyPool[9].set[2] = &mEnemyFormation[22]; + mEnemyPool[9].set[3] = &mEnemyFormation[23]; + mEnemyPool[9].set[4] = &mEnemyFormation[24]; + mEnemyPool[9].set[5] = &mEnemyFormation[70]; + mEnemyPool[9].set[6] = &mEnemyFormation[71]; + mEnemyPool[9].set[7] = &mEnemyFormation[72]; + mEnemyPool[9].set[8] = &mEnemyFormation[73]; + mEnemyPool[9].set[9] = &mEnemyFormation[74]; +} + +// Inicializa las fases del juego +void GameDirector::initGameStages() +{ + // STAGE 1 + mGame.stage[0].number = 1; + mGame.stage[0].currentPower = 0; + mGame.stage[0].powerToComplete = 200; + mGame.stage[0].minMenace = 7 + (4 * 1); + mGame.stage[0].maxMenace = 7 + (4 * 3); + mGame.stage[0].enemyPool = &mEnemyPool[0]; + + // STAGE 2 + mGame.stage[1].number = 2; + mGame.stage[1].currentPower = 0; + mGame.stage[1].powerToComplete = 300; + mGame.stage[1].minMenace = 7 + (4 * 2); + mGame.stage[1].maxMenace = 7 + (4 * 4); + mGame.stage[1].enemyPool = &mEnemyPool[1]; + + // STAGE 3 + mGame.stage[2].number = 3; + mGame.stage[2].currentPower = 0; + mGame.stage[2].powerToComplete = 600; + mGame.stage[2].minMenace = 7 + (4 * 3); + mGame.stage[2].maxMenace = 7 + (4 * 6); + mGame.stage[2].enemyPool = &mEnemyPool[2]; + + // STAGE 4 + mGame.stage[3].number = 4; + mGame.stage[3].currentPower = 0; + mGame.stage[3].powerToComplete = 600; + mGame.stage[3].minMenace = 7 + (4 * 3); + mGame.stage[3].maxMenace = 7 + (4 * 7); + mGame.stage[3].enemyPool = &mEnemyPool[3]; + + // STAGE 5 + mGame.stage[4].number = 5; + mGame.stage[4].currentPower = 0; + mGame.stage[4].powerToComplete = 600; + mGame.stage[4].minMenace = 7 + (4 * 4); + mGame.stage[4].maxMenace = 7 + (4 * 8); + mGame.stage[4].enemyPool = &mEnemyPool[4]; + + // STAGE 6 + mGame.stage[5].number = 6; + mGame.stage[5].currentPower = 0; + mGame.stage[5].powerToComplete = 600; + mGame.stage[5].minMenace = 7 + (4 * 5); + mGame.stage[5].maxMenace = 7 + (4 * 10); + mGame.stage[5].enemyPool = &mEnemyPool[5]; + + // STAGE 7 + mGame.stage[6].number = 7; + mGame.stage[6].currentPower = 0; + mGame.stage[6].powerToComplete = 650; + mGame.stage[6].minMenace = 7 + (4 * 6); + mGame.stage[6].maxMenace = 7 + (4 * 11); + mGame.stage[6].enemyPool = &mEnemyPool[6]; + + // STAGE 8 + mGame.stage[7].number = 8; + mGame.stage[7].currentPower = 0; + mGame.stage[7].powerToComplete = 750; + mGame.stage[7].minMenace = 7 + (4 * 7); + mGame.stage[7].maxMenace = 7 + (4 * 12); + mGame.stage[7].enemyPool = &mEnemyPool[7]; + + // STAGE 9 + mGame.stage[8].number = 9; + mGame.stage[8].currentPower = 0; + mGame.stage[8].powerToComplete = 850; + mGame.stage[8].minMenace = 7 + (4 * 8); + mGame.stage[8].maxMenace = 7 + (4 * 13); + mGame.stage[8].enemyPool = &mEnemyPool[8]; + + // STAGE 10 + mGame.stage[9].number = 10; + mGame.stage[9].currentPower = 0; + mGame.stage[9].powerToComplete = 950; + mGame.stage[9].minMenace = 7 + (4 * 9); + mGame.stage[9].maxMenace = 7 + (4 * 15); + mGame.stage[9].enemyPool = &mEnemyPool[9]; +} + +// Crea una formación de enemigos +void GameDirector::deployEnemyFormation() +{ + // Solo despliega una formación enemiga si ha pasado cierto tiempo desde la última + if (mGame.enemyDeployCounter == 0) + { + mGame.enemyDeployCounter = 255; + + Uint8 set = (rand() % 10); + + if (set == mGame.lastEnemyDeploy) + { + set++; + set %= 10; + } + mGame.lastEnemyDeploy = set; + + if (mDebug.enabled) + set = mDebug.enemySet; + + Uint8 numEnemies = mGame.stage[mGame.currentStage].enemyPool->set[set]->numberOfEnemies; + for (Uint8 i = 0; i < numEnemies; i++) + createNewBalloon(mGame.stage[mGame.currentStage].enemyPool->set[set]->init[i].x, + mGame.stage[mGame.currentStage].enemyPool->set[set]->init[i].y, + mGame.stage[mGame.currentStage].enemyPool->set[set]->init[i].kind, + mGame.stage[mGame.currentStage].enemyPool->set[set]->init[i].velX, + mGame.enemySpeed, + mGame.stage[mGame.currentStage].enemyPool->set[set]->init[i].creationCounter, + mTexture[TEXTURE_BALLOON].texture); + } +} + +// Aumenta el poder de la fase +void GameDirector::increaseStageCurrentPower() +{ + mGame.stage[mGame.currentStage].currentPower++; +} + // Establece el valor de la variable void GameDirector::setScore(Uint32 score) { @@ -1531,32 +2317,39 @@ void GameDirector::updateHiScore() // Transforma un valor numérico en una cadena de 6 cifras std::string GameDirector::updateScoreText(Uint32 num) { + return (std::to_string(num)); + if ((num >= 0) && (num <= 9)) { - return ("00000" + std::to_string(num)); + return ("000000" + std::to_string(num)); } if ((num >= 10) && (num <= 99)) { - return ("0000" + std::to_string(num)); + return ("00000" + std::to_string(num)); } if ((num >= 100) && (num <= 999)) { - return ("000" + std::to_string(num)); + return ("0000" + std::to_string(num)); } if ((num >= 1000) && (num <= 9999)) { - return ("00" + std::to_string(num)); + return ("000" + std::to_string(num)); } if ((num >= 010000) && (num <= 99999)) { - return ("0" + std::to_string(num)); + return ("00" + std::to_string(num)); } if ((num >= 100000) && (num <= 999999)) + { + return ("0" + std::to_string(num)); + } + + if ((num >= 1000000) && (num <= 9999999)) { return (std::to_string(num)); } @@ -1567,31 +2360,81 @@ std::string GameDirector::updateScoreText(Uint32 num) // Pinta el marcador en pantalla usando un objeto texto void GameDirector::renderScoreBoard() { - mText.white->write(SCORE_WORD_X, SCORE_WORD_Y, "SCORE", 0); - mText.white->write(SCORE_NUMBER_X, SCORE_NUMBER_Y, updateScoreText(mGame.score), 0); + // Si el jugador esta muerto, no pintes el fondo del marcador, así que pase por encima cuando sale despedido + if (mGame.player->isAlive()) + mGame.scoreBoard->render(); - mText.white->write(MULTIPLIER_WORD_X, MULTIPLIER_WORD_Y, "MULT", 0); - mText.white->write(MULTIPLIER_NUMBER_X, MULTIPLIER_NUMBER_Y, std::to_string(mGame.player->getScoreMultiplier()), 0, 3); + if (!mDemo.enabled) + { + // SCORE + mText.white->writeCentered(PLAY_AREA_CENTER_FIRST_QUARTER_X, SCORE_WORD_Y - 6, "SCORE", 0); + mText.white->writeCentered(PLAY_AREA_CENTER_FIRST_QUARTER_X, SCORE_NUMBER_Y - 6, updateScoreText(mGame.score), 0); - mText.white->write(HISCORE_WORD_X, HISCORE_WORD_Y, "HI-SCORE", 0); - mText.white->write(HISCORE_NUMBER_X, HISCORE_NUMBER_Y, updateScoreText(mGame.hiScore), 0); + // HI-SCORE + mText.white->writeCentered(PLAY_AREA_CENTER_THIRD_QUARTER_X, HISCORE_WORD_Y - 6, "HI-SCORE", 0); + mText.white->writeCentered(PLAY_AREA_CENTER_THIRD_QUARTER_X, HISCORE_NUMBER_Y - 6, updateScoreText(mGame.hiScore), 0); + + // MULT + mText.white->writeColored(MULTIPLIER_WORD_X, MULTIPLIER_WORD_Y - 6, "MULT", 0, 255, 0); + mText.whiteX2->writeShadowed(PLAY_AREA_CENTER_X - 16, SCORE_WORD_Y + 5, std::to_string(mGame.player->getScoreMultiplier()).substr(0, 1), 0, 192, 0); + mText.white->writeShadowed(PLAY_AREA_CENTER_X - 2, SCORE_WORD_Y + 12, ".", 0, 192, 0); + mText.white->writeShadowed(PLAY_AREA_CENTER_X + 4, SCORE_WORD_Y + 12, std::to_string(mGame.player->getScoreMultiplier()).substr(2, 1), 0, 192, 0); + + // STAGE + mText.white->writeCentered(PLAY_AREA_CENTER_FIRST_QUARTER_X, SCORE_NUMBER_Y + 4, "STAGE " + std::to_string(mGame.stage[mGame.currentStage].number), 0); + + // POWER + mGame.powerMeter->setSpriteClip(256, 184, 40, 8); + mGame.powerMeter->render(); + float percent = (mGame.stage[mGame.currentStage].currentPower * 40.0f) / mGame.stage[mGame.currentStage].powerToComplete; + mGame.powerMeter->setSpriteClip(296, 184, (int)percent, 8); + mGame.powerMeter->render(); + } + else + { + mText.nokia->writeCentered(SCREEN_FIRST_QUARTER_X, SCORE_WORD_Y - 3, "A game by", -1); + mText.nokia->writeCentered(SCREEN_FIRST_QUARTER_X, SCORE_NUMBER_Y, "JailDesigner", -1); + mText.nokia->writeCentered(SCREEN_THIRD_QUARTER_X, HISCORE_WORD_Y - 3, "Intro music by", -1); + mText.nokia->writeCentered(SCREEN_THIRD_QUARTER_X, HISCORE_NUMBER_Y, "JailDoctor", -1); + } } -// Actualiza el valor de la variable mGame.stage +// Actualiza las variables del jugador +void GameDirector::updatePlayer() +{ + mGame.player->update(); + + // Comprueba la colisión entre el jugador y los globos + if (checkPlayerBalloonCollision()) + { + if (mGame.player->isAlive()) + { + if (mDemo.enabled) + setProgSection(PROG_SECTION_TITLE, TITLE_SECTION_INSTRUCTIONS); + else + killPlayer(); + } + } +} + +// Actualiza las variables de la fase void GameDirector::updateStage() { - Uint8 realStage; - realStage = (mGame.score / 10000) + 1; - if (realStage > mGame.stage) + if (mGame.stage[mGame.currentStage].currentPower >= mGame.stage[mGame.currentStage].powerToComplete) { - mGame.stage = realStage; + // Cambio de fase + mGame.currentStage++; JA_PlaySound(mSound[SOUND_STAGE_CHANGE].sound); - mGame.stageCounter = 0; + mGame.stageBitmapCounter = 0; + mGame.enemySpeed = BALLOON_SPEED_1; + setBalloonSpeed(mGame.enemySpeed); + mGame.effect.flash = true; + mGame.effect.shake = true; } - if (mGame.stageCounter < STAGE_COUNTER) + if (mGame.stageBitmapCounter < STAGE_COUNTER) { - mGame.stageCounter++; + mGame.stageBitmapCounter++; } } @@ -1686,16 +2529,11 @@ void GameDirector::renderDeathFade() } } -// Mueve todos los globos activos -void GameDirector::moveBalloons() +// Actualiza los globos +void GameDirector::updateBalloons() { for (Uint8 i = 0; i < MAX_BALLOONS; i++) - { - if (mGame.balloon[i]->isActive()) - { - mGame.balloon[i]->update(); - } - } + mGame.balloon[i]->update(); } // Pinta en pantalla todos los globos activos @@ -1703,59 +2541,133 @@ void GameDirector::renderBalloons() { for (Uint8 i = 0; i < MAX_BALLOONS; i++) { - if (mGame.balloon[i]->isActive()) - { - mGame.balloon[i]->render(); - if ((mProg.debug) && (mGame.balloon[i]->isPopping() == false)) - mText.white->write(mGame.balloon[i]->getPosX() + (mGame.balloon[i]->getWidth() / 2) - 4, mGame.balloon[i]->getPosY() - 8, std::to_string(i)); - } + mGame.balloon[i]->render(); + if ((mDebug.enabled) && (mGame.balloon[i]->isPopping() == false)) + mText.white->writeCentered(mGame.balloon[i]->getPosX() + (mGame.balloon[i]->getWidth() / 2), mGame.balloon[i]->getPosY() - 8, std::to_string(i), 0); } } // Devuelve el primer indice no activo del vector de globos Uint8 GameDirector::getBalloonFreeIndex() { - int index = 0; - + Uint8 index = 0; for (Uint8 i = 0; i < MAX_BALLOONS; i++) - { - if (mGame.balloon[i]->isActive() == false) + if (!mGame.balloon[i]->isEnabled()) { index = i; break; } - } - return index; } // Crea un globo nuevo en el vector de globos -Uint8 GameDirector::createNewBalloon(float x, int y, Uint8 kind, float velx, Uint16 creationtimer, LTexture *texture) +Uint8 GameDirector::createNewBalloon(float x, int y, Uint8 kind, float velx, float speed, Uint16 creationtimer, LTexture *texture) { Uint8 index = getBalloonFreeIndex(); - mGame.balloon[index]->init(x, y, kind, velx, creationtimer, texture, mRenderer); + mGame.balloon[index]->init(x, y, kind, velx, speed, creationtimer, texture, mRenderer); return index; } +// Crea una PowerBall +void GameDirector::createPowerBall() +{ + const int y4 = (PLAY_AREA_TOP - BLOCK); + const int x4_0 = PLAY_AREA_LEFT; + const int x4_50 = PLAY_AREA_CENTER_X - (BALLOON_SIZE_4 / 2); + const int x4_100 = (PLAY_AREA_RIGHT)-BALLOON_SIZE_4; + const int x[3] = {x4_0, x4_50, x4_100}; + int x4 = x[rand() % 3]; + + mGame.balloon[getBalloonFreeIndex()]->init(x4, y4, POWER_BALL, BALLOON_VELX_POSITIVE * (((rand() % 2) * 2) - 1), mGame.enemySpeed, 350, mTexture[TEXTURE_BALLOON].texture, mRenderer); +} + // Establece a cero todos los valores del vector de objetos globo void GameDirector::resetBalloons() { for (Uint8 i = 0; i < MAX_BALLOONS; i++) { - mGame.balloon[i]->erase(); + mGame.balloon[i]->disable(); + } +} + +// Establece la velocidad de los globos +void GameDirector::setBalloonSpeed(float speed) +{ + for (Uint8 i = 0; i < MAX_BALLOONS; i++) + { + if (mGame.balloon[i]->isEnabled()) + mGame.balloon[i]->setSpeed(speed); + } +} + +// Incrementa la velocidad de los globos +void GameDirector::incBalloonSpeed() +{ + if (mGame.enemySpeed == BALLOON_SPEED_1) + mGame.enemySpeed = BALLOON_SPEED_2; + else if (mGame.enemySpeed == BALLOON_SPEED_2) + mGame.enemySpeed = BALLOON_SPEED_3; + else if (mGame.enemySpeed == BALLOON_SPEED_3) + mGame.enemySpeed = BALLOON_SPEED_4; + else if (mGame.enemySpeed == BALLOON_SPEED_4) + mGame.enemySpeed = BALLOON_SPEED_5; + setBalloonSpeed(mGame.enemySpeed); +} + +// Decrementa la velocidad de los globos +void GameDirector::decBalloonSpeed() +{ + if (mGame.enemySpeed == BALLOON_SPEED_5) + mGame.enemySpeed = BALLOON_SPEED_4; + else if (mGame.enemySpeed == BALLOON_SPEED_4) + mGame.enemySpeed = BALLOON_SPEED_3; + else if (mGame.enemySpeed == BALLOON_SPEED_3) + mGame.enemySpeed = BALLOON_SPEED_2; + else if (mGame.enemySpeed == BALLOON_SPEED_2) + mGame.enemySpeed = BALLOON_SPEED_1; + setBalloonSpeed(mGame.enemySpeed); +} + +// Actualiza la velocidad de los globos en funcion del poder acumulado de la fase +void GameDirector::updateBalloonSpeed() +{ + float percent = (float)mGame.stage[mGame.currentStage].currentPower / (float)mGame.stage[mGame.currentStage].powerToComplete; + if (mGame.enemySpeed == BALLOON_SPEED_1) + { + if (percent > 0.2f) + incBalloonSpeed(); + } + else if (mGame.enemySpeed == BALLOON_SPEED_2) + { + if (percent > 0.4f) + incBalloonSpeed(); + } + else if (mGame.enemySpeed == BALLOON_SPEED_3) + { + if (percent > 0.6f) + incBalloonSpeed(); + } + else if (mGame.enemySpeed == BALLOON_SPEED_4) + { + if (percent > 0.8f) + incBalloonSpeed(); } } // Explosiona un globo. Lo destruye y crea otros dos si es el caso void GameDirector::popBalloon(Uint8 index) { - if (mGame.balloon[index]->isActive()) + //if (mGame.balloon[index]->isActive()) { // Otorga los puntos correspondientes al globo mGame.player->addScore(Uint32(mGame.balloon[index]->getScore() * mGame.player->getScoreMultiplier())); setScore(mGame.player->getScore()); updateHiScore(); + // Aumenta el poder de la fase + increaseStageCurrentPower(); + mGame.balloonsPopped++; + Uint8 kind = mGame.balloon[index]->getKind(); Uint8 freeIndex = 0; switch (kind) @@ -1765,29 +2677,62 @@ void GameDirector::popBalloon(Uint8 index) mGame.balloon[index]->pop(); break; + case HEXAGON_1: + mGame.balloon[index]->pop(); + break; + + // Si es del tipo PowerBall, destruye todos los globos + case POWER_BALL: + destroyAllBalloons(); + break; + // En cualquier otro caso, crea dos globos de un tipo inferior default: freeIndex = getBalloonFreeIndex(); - mGame.balloon[freeIndex]->init(0, mGame.balloon[index]->getPosY(), mGame.balloon[index]->getKind() - 1, BALLOON_VELX_NEGATIVE, 0, mTexture[TEXTURE_BALLOON].texture, mRenderer); + mGame.balloon[freeIndex]->init(0, mGame.balloon[index]->getPosY(), mGame.balloon[index]->getKind() - 1, BALLOON_VELX_NEGATIVE, mGame.enemySpeed, 0, mTexture[TEXTURE_BALLOON].texture, mRenderer); mGame.balloon[freeIndex]->allignTo(mGame.balloon[index]->getPosX() + (mGame.balloon[index]->getWidth() / 2)); - mGame.balloon[freeIndex]->setVelY(-2.5); - // if (isTimeStopped()) mGame.balloon[freeIndex]->setStop(true); + if (mGame.balloon[freeIndex]->getClass() == BALLOON_CLASS) + mGame.balloon[freeIndex]->setVelY(-2.50f); + else if (mGame.balloon[freeIndex]->getClass() == HEXAGON_CLASS) + mGame.balloon[freeIndex]->setVelY(BALLOON_VELX_NEGATIVE); freeIndex = getBalloonFreeIndex(); - mGame.balloon[freeIndex]->init(0, mGame.balloon[index]->getPosY(), mGame.balloon[index]->getKind() - 1, BALLOON_VELX_POSITIVE, 0, mTexture[TEXTURE_BALLOON].texture, mRenderer); + mGame.balloon[freeIndex]->init(0, mGame.balloon[index]->getPosY(), mGame.balloon[index]->getKind() - 1, BALLOON_VELX_POSITIVE, mGame.enemySpeed, 0, mTexture[TEXTURE_BALLOON].texture, mRenderer); mGame.balloon[freeIndex]->allignTo(mGame.balloon[index]->getPosX() + (mGame.balloon[index]->getWidth() / 2)); - mGame.balloon[freeIndex]->setVelY(-2.5); - // if (isTimeStopped()) mGame.balloon[freeIndex]->setStop(true); + if (mGame.balloon[freeIndex]->getClass() == BALLOON_CLASS) + mGame.balloon[freeIndex]->setVelY(-2.50f); + else if (mGame.balloon[freeIndex]->getClass() == HEXAGON_CLASS) + mGame.balloon[freeIndex]->setVelY(BALLOON_VELX_NEGATIVE); // Elimina el globo mGame.balloon[index]->pop(); break; } - // Reproduce el sonido de explotar el globo - // JA_PlaySound(mFX.popBalloon); // Recalcula el nivel de amenaza - setMenaceLevel(); + setMenace(); + } +} + +// Explosiona un globo. Lo destruye +void GameDirector::destroyBalloon(Uint8 index) +{ + //if (mGame.balloon[index]->isActive()) + { + // Otorga los puntos correspondientes al globo + mGame.player->addScore(Uint32(mGame.balloon[index]->getScore() * mGame.player->getScoreMultiplier())); + setScore(mGame.player->getScore()); + updateHiScore(); + + // Aumenta el poder de la fase + increaseStageCurrentPower(); + mGame.balloonsPopped++; + + // Destruye el globo + mGame.balloon[index]->pop(); + + // Recalcula el nivel de amenaza + setMenace(); } } @@ -1798,50 +2743,59 @@ void GameDirector::popAllBalloons() Uint8 j = 0; for (Uint8 i = 0; i < MAX_BALLOONS; i++) { - if ((mGame.balloon[i]->isActive()) && (mGame.balloon[i]->isPopping() == false) && (mGame.balloon[i]->isBeingCreated() == false)) - { + if ((mGame.balloon[i]->isEnabled()) && (!mGame.balloon[i]->isPopping()) && (!mGame.balloon[i]->isBeingCreated())) candidate[j] = i; - } else - { candidate[j] = -1; - } - j += 1; + j++; } for (Uint8 i = 0; i < MAX_BALLOONS; i++) - { if (candidate[i] >= 0) - { popBalloon(i); - } - } JA_PlaySound(mSound[SOUND_BALLOON].sound); } +// Destruye todos los globos +void GameDirector::destroyAllBalloons() +{ + int candidate[MAX_BALLOONS]; + Uint8 j = 0; + for (Uint8 i = 0; i < MAX_BALLOONS; i++) + { + if ((mGame.balloon[i]->isEnabled()) && (!mGame.balloon[i]->isPopping()) && (!mGame.balloon[i]->isBeingCreated())) + candidate[j] = i; + else + candidate[j] = -1; + j++; + } + for (Uint8 i = 0; i < MAX_BALLOONS; i++) + if (candidate[i] >= 0) + destroyBalloon(i); + JA_PlaySound(mSound[SOUND_BALLOON].sound); + mGame.effect.flash = true; + mGame.effect.shake = true; +} + // Detiene todos los globos void GameDirector::stopAllBalloons(Uint16 time) { for (Uint8 i = 0; i < MAX_BALLOONS; i++) - { - if (mGame.balloon[i]->isActive()) + if (mGame.balloon[i]->isEnabled()) { mGame.balloon[i]->setStop(true); mGame.balloon[i]->setStoppedTimer(time); } - } } // Pone en marcha todos los globos void GameDirector::startAllBalloons() { for (Uint8 i = 0; i < MAX_BALLOONS; i++) - { - if ((mGame.balloon[i]->isActive()) && (mGame.balloon[i]->isBeingCreated() == false)) + if ((mGame.balloon[i]->isEnabled()) && (!mGame.balloon[i]->isBeingCreated())) { mGame.balloon[i]->setStop(false); mGame.balloon[i]->setStoppedTimer(0); } - } } // Obtiene el numero de globos activos @@ -1849,12 +2803,9 @@ Uint8 GameDirector::countBalloons() { Uint8 num = 0; for (Uint8 i = 0; i < MAX_BALLOONS; i++) - { - if (mGame.balloon[i]->isActive()) - { - ++num; - } - } + if (mGame.balloon[i]->isEnabled()) + if (!mGame.balloon[i]->isPopping()) + num++; return num; } @@ -1865,15 +2816,12 @@ bool GameDirector::checkPlayerBalloonCollision() bool result = false; for (Uint8 i = 0; i < MAX_BALLOONS; i++) { - //if ((mGame.balloon[i]->isActive()) && (mGame.balloon[i]->isStopped() == false)) - if ((mGame.balloon[i]->isActive()) && !(mGame.balloon[i]->isStopped()) && !(mGame.balloon[i]->isInvulnerable())) - { + if ((mGame.balloon[i]->isEnabled()) && !(mGame.balloon[i]->isStopped()) && !(mGame.balloon[i]->isInvulnerable())) if (checkCollision(mGame.player->getCollider(), mGame.balloon[i]->getCollider())) { result = true; break; } - } } return result; } @@ -1894,21 +2842,21 @@ void GameDirector::checkPlayerItemCollision() mGame.player->addScore(1000); setScore(mGame.player->getScore()); updateHiScore(); - createItemScoreSprite(mGame.item[i]->getPosX() + (mGame.item[i]->getWidth() / 2) - (mGame._1000Bitmap->getWidth() / 2), mGame.item[i]->getPosY(), mGame._1000Bitmap); + createItemScoreSprite(mGame.item[i]->getPosX() + (mGame.item[i]->getWidth() / 2) - (mGame._1000Bitmap->getWidth() / 2), mGame.player->getPosY(), mGame._1000Bitmap); JA_PlaySound(mSound[SOUND_ITEM_PICKUP].sound); break; case ITEM_POINTS_2_GAVINA: mGame.player->addScore(2500); setScore(mGame.player->getScore()); updateHiScore(); - createItemScoreSprite(mGame.item[i]->getPosX() + (mGame.item[i]->getWidth() / 2) - (mGame._2500Bitmap->getWidth() / 2), mGame.item[i]->getPosY(), mGame._2500Bitmap); + createItemScoreSprite(mGame.item[i]->getPosX() + (mGame.item[i]->getWidth() / 2) - (mGame._2500Bitmap->getWidth() / 2), mGame.player->getPosY(), mGame._2500Bitmap); JA_PlaySound(mSound[SOUND_ITEM_PICKUP].sound); break; case ITEM_POINTS_3_PACMAR: mGame.player->addScore(5000); setScore(mGame.player->getScore()); updateHiScore(); - createItemScoreSprite(mGame.item[i]->getPosX() + (mGame.item[i]->getWidth() / 2) - (mGame._5000Bitmap->getWidth() / 2), mGame.item[i]->getPosY(), mGame._5000Bitmap); + createItemScoreSprite(mGame.item[i]->getPosX() + (mGame.item[i]->getWidth() / 2) - (mGame._5000Bitmap->getWidth() / 2), mGame.player->getPosY(), mGame._5000Bitmap); JA_PlaySound(mSound[SOUND_ITEM_PICKUP].sound); break; case ITEM_CLOCK: @@ -1938,30 +2886,31 @@ void GameDirector::checkPlayerItemCollision() void GameDirector::checkBulletBalloonCollision() { for (Uint8 i = 0; i < MAX_BALLOONS; i++) - { for (Uint8 j = 0; j < MAX_BULLETS; j++) - { - if (mGame.balloon[i]->isActive() && !(mGame.balloon[i]->isInvulnerable()) && mGame.bullet[j]->isActive()) - { + if (mGame.balloon[i]->isEnabled() && (!mGame.balloon[i]->isInvulnerable()) && mGame.bullet[j]->isActive()) if (checkCollision(mGame.balloon[i]->getCollider(), mGame.bullet[j]->getCollider())) { mGame.player->incScoreMultiplier(); popBalloon(i); - if (mDemo.enabled == false) + if (!mDemo.enabled) JA_PlaySound(mSound[SOUND_BALLOON].sound); mGame.bullet[j]->erase(); - setMenaceLevel(); + setMenace(); Uint8 droppeditem = dropItem(); - if ((droppeditem != NO_KIND) && (mDemo.enabled == false) && (mDemo.recording == false)) + if ((droppeditem != NO_KIND) && !(mDemo.enabled) && !(mDemo.recording)) { - createItem(mGame.balloon[i]->getPosX(), mGame.balloon[i]->getPosY(), droppeditem); - JA_PlaySound(mSound[SOUND_ITEM_DROP].sound); + if (droppeditem == ITEM_POWER_BALL) + { + createPowerBall(); + } + else + { + createItem(mGame.balloon[i]->getPosX(), mGame.balloon[i]->getPosY(), droppeditem); + JA_PlaySound(mSound[SOUND_ITEM_DROP].sound); + } } break; } - } - } - } } // Mueve las balas activas @@ -2070,46 +3019,40 @@ void GameDirector::resetItems() // Devuelve un item en función del azar Uint8 GameDirector::dropItem() { - // return ITEM_COFFEE; + //return ITEM_COFFEE; + Uint8 luckyNumber = rand() % 99; - Uint8 item = rand() % 6; + Uint8 item = rand() % 7; switch (item) { case 0: if (luckyNumber < 10) - { return ITEM_POINTS_1_DISK; - } break; case 1: if (luckyNumber < 6) - { return ITEM_POINTS_2_GAVINA; - } break; case 2: if (luckyNumber < 3) - { return ITEM_POINTS_3_PACMAR; - } break; case 3: if (luckyNumber < 5) - { return ITEM_CLOCK; - } break; case 4: if (luckyNumber < 5) - { - return ITEM_TNT; - } + return NO_KIND; break; case 5: if (luckyNumber < 5) - { return ITEM_COFFEE; - } + break; + case 6: + if (luckyNumber < 5) + if (countBalloons() > 10) + return ITEM_POWER_BALL; break; default: break; @@ -2136,6 +3079,36 @@ void GameDirector::createItemScoreSprite(int x, int y, SmartSprite *sprite) mGame.smartSprite[index]->setEnabledTimer(100); } +// Crea un objeto de bonus en función del azar +void GameDirector::dropBonus() +{ +} + +// Dibuja el efecto de flash +void GameDirector::renderFlashEffect() +{ + if (mGame.effect.flash) + { + // Pantallazo blanco + SDL_SetRenderDrawColor(mRenderer, 0xFF, 0xFF, 0xFF, 0xFF); + SDL_RenderClear(mRenderer); + + mGame.effect.flash = false; + } +} + +// Actualiza el efecto de agitar la pantalla +void GameDirector::updateShakeEffect() +{ + if (mGame.effect.shake) + { + if (mGame.effect.shakeCounter > 0) + mGame.effect.shakeCounter--; + else + mGame.effect.shake = false; + } +} + // Crea un SmartSprite para arrojar el item café al recibir un impacto void GameDirector::throwCoffee(int x, int y) { @@ -2278,22 +3251,25 @@ void GameDirector::createCoffeDrops(Uint8 num, int x, int y) // Acciones a realizar cuando el jugador muere void GameDirector::killPlayer() { - if (mGame.player->hasExtraHit()) + if (!mGame.player->isInvulnerable()) { - mGame.player->removeExtraHit(); - throwCoffee(mGame.player->getPosX() + (mGame.player->getWidth() / 2), mGame.player->getPosY() + (mGame.player->getHeight() / 2)); - JA_PlaySound(mSound[SOUND_COFFEE_OUT].sound); - } - else if (!mGame.player->isInvulnerable()) - { - stopAllBalloons(10); // 5000 - JA_StopMusic(); - JA_PlaySound(mSound[SOUND_PLAYER_COLLISION].sound); - shakeScreen(); - SDL_Delay(500); // 1000 - JA_PlaySound(mSound[SOUND_COFFEE_OUT].sound); - throwPlayer(mGame.player->getPosX(), mGame.player->getPosY()); - mGame.player->setAlive(false); + if (mGame.player->hasExtraHit()) + { + mGame.player->removeExtraHit(); + throwCoffee(mGame.player->getPosX() + (mGame.player->getWidth() / 2), mGame.player->getPosY() + (mGame.player->getHeight() / 2)); + JA_PlaySound(mSound[SOUND_COFFEE_OUT].sound); + } + else + { + stopAllBalloons(10); // 5000 + JA_StopMusic(); + JA_PlaySound(mSound[SOUND_PLAYER_COLLISION].sound); + shakeScreen(); + SDL_Delay(500); // 1000 + JA_PlaySound(mSound[SOUND_COFFEE_OUT].sound); + throwPlayer(mGame.player->getPosX(), mGame.player->getPosY()); + mGame.player->setAlive(false); + } } } @@ -2304,19 +3280,18 @@ Uint8 GameDirector::getSubsection() } // Calcula y establece el valor de amenaza en funcion de los globos activos -void GameDirector::setMenaceLevel() +void GameDirector::setMenace() { - mGame.menaceLevelCurrent = 0; + mGame.menaceCurrent = 0; for (Uint8 i = 0; i < MAX_BALLOONS; i++) - { - mGame.menaceLevelCurrent += mGame.balloon[i]->getMenace(); - } + if (mGame.balloon[i]->isEnabled()) + mGame.menaceCurrent += mGame.balloon[i]->getMenace(); } // Obtiene el valor de la variable -Uint8 GameDirector::getMenaceLevel() +Uint8 GameDirector::getMenace() { - return mGame.menaceLevelCurrent; + return mGame.menaceCurrent; } // Establece el valor de la variable @@ -2396,62 +3371,60 @@ void GameDirector::updateRemainingExplosionsCounter() } } +// Actualiza la variable EnemyDeployCounter +void GameDirector::updateEnemyDeployCounter() +{ + if (mGame.enemyDeployCounter > 0) + mGame.enemyDeployCounter--; +} + // Actualiza el campo de juego void GameDirector::updatePlayField() { - // Comprueba el teclado/mando - checkGameInput(); + // Comprueba el teclado/mando + checkGameInput(); - // Actualiza el jugador - mGame.player->update(); + // Actualiza las variables del jugador + updatePlayer(); - // Actualiza el fondo - updateBackground(); + // Actualiza el fondo + updateBackground(); - // Mueve los globos - moveBalloons(); + // Mueve los globos + updateBalloons(); - // Mueve las balas - moveBullets(); + // Mueve las balas + moveBullets(); - // Actualiza los items - updateItems(); + // Actualiza los items + updateItems(); - // Actualiza el valor de mGame.stage - updateStage(); + // Actualiza el valor de mGame.currentStage + updateStage(); - // Actualiza el estado de muerte - updateDeath(); + // Actualiza el estado de muerte + updateDeath(); - // Actualiza los SmartSprites - updateSmartSprites(); + // Actualiza los SmartSprites + updateSmartSprites(); - // Actualiza los contadores de estado - updateTimeStoppedCounter(); - updateRemainingExplosionsCounter(); + // Actualiza los contadores de estado y efectos + updateTimeStoppedCounter(); + updateRemainingExplosionsCounter(); + updateEnemyDeployCounter(); + updateShakeEffect(); - // Comprueba las colisiones entre globos y balas - checkBulletBalloonCollision(); + // Comprueba las colisiones entre globos y balas + checkBulletBalloonCollision(); - // Comprueba las colisiones entre elk jugador y los items - checkPlayerItemCollision(); + // Comprueba las colisiones entre elk jugador y los items + checkPlayerItemCollision(); - // Comprueba el nivel de amenaza - updateMenaceLevel(); + // Comprueba el nivel de amenaza + updateMenace(); - // Comprueba la colisión entre el jugador y los globos - if (checkPlayerBalloonCollision()) - { - if (mGame.player->isAlive()) - { - if (mDemo.enabled) - setProgSection(PROG_SECTION_TITLE, TITLE_SECTION_INSTRUCTIONS); - else - killPlayer(); - } - } - // Actualiza el mensaje de GetReady - mGame.getReadyBitmap->update(); + // Actualiza la velocidad de los enemigos + updateBalloonSpeed(); } // Actualiza el fondo @@ -2462,48 +3435,44 @@ void GameDirector::updateBackground() mGame.clouds2a->move(); mGame.clouds2b->move(); if (mGame.clouds1a->getPosX() < -mGame.clouds1a->getWidth()) - { mGame.clouds1a->setPosX(mGame.clouds1a->getWidth()); - } if (mGame.clouds1b->getPosX() < -mGame.clouds1b->getWidth()) - { mGame.clouds1b->setPosX(mGame.clouds1b->getWidth()); - } if (mGame.clouds2a->getPosX() < -mGame.clouds2a->getWidth()) - { mGame.clouds2a->setPosX(mGame.clouds2a->getWidth()); - } if (mGame.clouds2b->getPosX() < -mGame.clouds2b->getWidth()) - { mGame.clouds2b->setPosX(mGame.clouds2b->getWidth()); - } mGame.grass->setSpriteClip(256, 85 + (6 * (mGame.counter / 20 % 2)), SCREEN_WIDTH, 6); + if (mGame.effect.shake) + mGame.background->setPosX(((mGame.effect.shakeCounter % 2) * 2) - 1); + else + mGame.background->setPosX(0); } // Dibuja el fondo void GameDirector::renderBackground() { - int alpha = (255 - (mGame.score / 392)); + float gradientNumber = std::min(((float)mGame.balloonsPopped / 900.0f), 3.0f); + float percent = gradientNumber - (int)gradientNumber; + int alpha = std::max((255 - (int)(255 * percent)), 0); - if (alpha < 0) - { - alpha = 0; - } - - mGame.gradient->setSpriteClip(SCREEN_WIDTH, 192, SCREEN_WIDTH, SCREEN_HEIGHT); - mTexture[TEXTURE_GAME_BG].texture->setAlpha(255); + // Dibuja el gradiente 2 + mGame.gradient->setSpriteClip(mGame.gradientRect[((int)gradientNumber + 1) % 4]); mGame.gradient->render(); - mGame.gradient->setSpriteClip(0, 192, SCREEN_WIDTH, SCREEN_HEIGHT); + // Dibuja el gradiente 1 con una opacidad cada vez menor + mGame.gradient->setSpriteClip(mGame.gradientRect[(int)gradientNumber]); mTexture[TEXTURE_GAME_BG].texture->setAlpha(alpha); mGame.gradient->render(); - mTexture[TEXTURE_GAME_BG].texture->setAlpha(255); + mGame.clouds1a->render(); mGame.clouds1b->render(); mGame.clouds2a->render(); mGame.clouds2b->render(); + mGame.background->render(); + mGame.grass->render(); } @@ -2516,30 +3485,20 @@ void GameDirector::renderPlayField() renderItems(); renderSmartSprites(); mGame.player->render(); - renderDeathFade(); - if (!mDemo.enabled) - { - renderScoreBoard(); - } - else - { - mText.nokia->writeCentered(SCREEN_FIRST_QUARTER_X, SCORE_WORD_Y - 3, "A game by", -1); - mText.nokia->writeCentered(SCREEN_FIRST_QUARTER_X, SCORE_NUMBER_Y, "JailDesigner", -1); - mText.nokia->writeCentered(SCREEN_THIRD_QUARTER_X, HISCORE_WORD_Y - 3, "Intro music by", -1); - mText.nokia->writeCentered(SCREEN_THIRD_QUARTER_X, HISCORE_NUMBER_Y, "JailDoctor", -1); - } renderMessages(); - renderDebugInfo(); + renderDeathFade(); + renderScoreBoard(); + renderFlashEffect(); } // Gestiona el nivel de amenaza -void GameDirector::updateMenaceLevel() +void GameDirector::old_updateMenace() { // Aumenta el nivel de amenaza en función de la puntuación - mGame.menaceLevelThreshold = 7 + (4 * (mGame.score / 10000)); + mGame.menaceThreshold = 7 + (4 * (mGame.score / 10000)); // Si el nivel de amenza es inferior al umbral - if (mGame.menaceLevelCurrent < mGame.menaceLevelThreshold) + if (mGame.menaceCurrent < mGame.menaceThreshold) { Uint8 index = 0; @@ -2549,16 +3508,36 @@ void GameDirector::updateMenaceLevel() // Crea un globo sobre el jugador en dirección hacia el centro if (x < (PLAY_AREA_WIDTH / 2)) { - index = createNewBalloon(0, PLAY_AREA_TOP + BLOCK - 37, BALLOON_4, BALLOON_VELX_POSITIVE, 400, mTexture[TEXTURE_BALLOON].texture); + index = createNewBalloon(0, PLAY_AREA_TOP + BLOCK - 37, BALLOON_4, BALLOON_VELX_POSITIVE, mGame.enemySpeed, 400, mTexture[TEXTURE_BALLOON].texture); } else { - index = createNewBalloon(0, PLAY_AREA_TOP + BLOCK - 37, BALLOON_4, BALLOON_VELX_NEGATIVE, 400, mTexture[TEXTURE_BALLOON].texture); + index = createNewBalloon(0, PLAY_AREA_TOP + BLOCK - 37, BALLOON_4, BALLOON_VELX_NEGATIVE, mGame.enemySpeed, 400, mTexture[TEXTURE_BALLOON].texture); } mGame.balloon[index]->allignTo(x); // Recalcula el nivel de amenaza con el nuevo globo - setMenaceLevel(); + setMenace(); + } +} + +// Gestiona el nivel de amenaza +void GameDirector::updateMenace() +{ + float percent = mGame.stage[mGame.currentStage].currentPower / mGame.stage[mGame.currentStage].powerToComplete; + Uint8 difference = mGame.stage[mGame.currentStage].maxMenace - mGame.stage[mGame.currentStage].minMenace; + + // Aumenta el nivel de amenaza en función de la puntuación + mGame.menaceThreshold = mGame.stage[mGame.currentStage].minMenace + (difference * percent); + + // Si el nivel de amenza es inferior al umbral + if (mGame.menaceCurrent < mGame.menaceThreshold) + { + // Crea una formación de enemigos + deployEnemyFormation(); + + // Recalcula el nivel de amenaza con el nuevo globo + setMenace(); } } @@ -2992,8 +3971,12 @@ void GameDirector::renderFade(Uint8 index) // Pinta diferentes mensajes en la pantalla void GameDirector::renderMessages() { - // GETREADY! - mGame.getReadyBitmap->render(); + // GetReady + if (mGame.counter < STAGE_COUNTER) + { + mGame.getReadyBitmap->setPosX((int)mGame.getReadyBitmapPath[mGame.counter]); + mGame.getReadyBitmap->render(); + } // Time Stopped if (mGame.timeStopped) @@ -3016,10 +3999,10 @@ void GameDirector::renderMessages() } // STAGE NUMBER - if (mGame.stageCounter < STAGE_COUNTER) + if (mGame.stageBitmapCounter < STAGE_COUNTER) { - mText.blackX2->writeCentered(PLAY_AREA_CENTER_X + 2, mGame.stagePath[mGame.stageCounter] + 2, "STAGE " + std::to_string(mGame.stage)); - mText.whiteX2->writeCentered(PLAY_AREA_CENTER_X, mGame.stagePath[mGame.stageCounter], "STAGE " + std::to_string(mGame.stage)); + mText.blackX2->writeCentered(PLAY_AREA_CENTER_X + 2, mGame.stageBitmapPath[mGame.stageBitmapCounter] + 2, "STAGE " + std::to_string(mGame.stage[mGame.currentStage].number)); + mText.whiteX2->writeCentered(PLAY_AREA_CENTER_X, mGame.stageBitmapPath[mGame.stageBitmapCounter], "STAGE " + std::to_string(mGame.stage[mGame.currentStage].number)); } } @@ -3130,6 +4113,44 @@ void GameDirector::shakeScreen() } } +// Agita la pantalla +void GameDirector::shakeScreen2() +{ + int v[] = {-1, 1, -1, 1, -1, 1, -1, 0}; + for (Uint8 n = 0; n < 8; n++) + { + // Limpia la pantalla + SDL_SetRenderDrawColor(mRenderer, 0x00, 0x00, 0x00, 0xFF); + SDL_RenderClear(mRenderer); + + // Dibuja los objetos + mGame.background->setPosX(0); + mGame.background->setWidth(1); + mGame.background->setSpriteClip(0, 0, 1, 192); + renderBackground(); + + mGame.background->setPosX(255); + mGame.background->setSpriteClip(255, 0, 1, 192); + mGame.background->render(); + + mGame.background->setPosX(v[n]); + mGame.background->setWidth(256); + mGame.background->setSpriteClip(0, 0, 256, 192); + mGame.background->render(); + + mGame.grass->render(); + renderBalloons(); + renderBullets(); + renderItems(); + mGame.player->render(); + renderScoreBoard(); + + // Actualiza la pantalla + SDL_RenderPresent(mRenderer); + SDL_Delay(50); + } +} + // Bucle para el logo del juego void GameDirector::runLogo() { @@ -3689,10 +4710,6 @@ void GameDirector::runTitle(Uint8 subsection) mMenu.active->reset(); toogleTitleNextGS(); renderFade(1); - if (mProg.section == PROG_SECTION_INTRO) - { - JA_StopMusic(); - } enableDemoMode(); } } @@ -3751,16 +4768,48 @@ void GameDirector::runGame() } else if ((mEventHandler->type == SDL_KEYDOWN) && (mEventHandler->key.repeat == 0)) { - switch (mEventHandler->key.keysym.scancode) + if (mDebug.enabled) { - case SDL_SCANCODE_T: - mProg.ticksSpeed *= 2; - break; - case SDL_SCANCODE_G: - mProg.ticksSpeed /= 2; - break; - default: - break; + switch (mEventHandler->key.keysym.scancode) + { + case SDL_SCANCODE_F: + mDebug.enemySet--; + if (mDebug.enemySet == 255) + mDebug.enemySet = 0; + break; + case SDL_SCANCODE_R: + mDebug.enemySet = std::min((int)++mDebug.enemySet, 9); + break; + case SDL_SCANCODE_T: + mProg.ticksSpeed *= 2; + break; + case SDL_SCANCODE_G: + createNewBalloon(100, 0, BALLOON_1, BALLOON_VELX_POSITIVE, 1.0F, 0, mTexture[TEXTURE_BALLOON].texture); + break; + case SDL_SCANCODE_P: + popAllBalloons(); + //mGame.balloonsPopped += 10; + break; + case SDL_SCANCODE_Z: + incBalloonSpeed(); + break; + case SDL_SCANCODE_X: + decBalloonSpeed(); + break; + case SDL_SCANCODE_C: + mDebug.gradB += 10; + break; + case SDL_SCANCODE_I: + mGame.player->setInvulnerable(true); + mGame.player->setInvulnerableCounter(65000); + break; + case SDL_SCANCODE_M: + break; + case SDL_SCANCODE_N: + break; + default: + break; + } } } } @@ -3785,8 +4834,8 @@ void GameDirector::runGame() // Volca el contenido de la textura en el renderizador SDL_RenderCopy(mRenderer, mBackbuffer, NULL, NULL); + renderDebugInfo(); // Actualiza la pantalla - //if (mProg.section == PROG_SECTION_GAME) SDL_RenderPresent(mRenderer); } } @@ -3797,7 +4846,7 @@ void GameDirector::runGame() // Bucle para el menu de pausa del juego void GameDirector::runPausedGame() { - while ((mGame.section == GAME_SECTION_PAUSE) && (mProg.section == PROG_SECTION_GAME)) + while ((mGame.section == GAME_SECTION_PAUSE) && (mProg.section == PROG_SECTION_GAME) && (!exit())) { // Comprueba los eventos que hay en la cola while (SDL_PollEvent(mEventHandler) != 0) @@ -3884,7 +4933,7 @@ void GameDirector::runInstructions() SDL_Rect rect = {0, 0, 16, 16}; sprite->init(rect1, mTexture[TEXTURE_ITEMS].texture, mRenderer); - while (mTitle.section == TITLE_SECTION_INSTRUCTIONS) + while ((mTitle.section == TITLE_SECTION_INSTRUCTIONS) && (!exit())) { int y = 0; // Comprueba los eventos que hay en la cola @@ -3977,6 +5026,7 @@ void GameDirector::runInstructions() { mTitle.instructionsCounter = INSTRUCTIONS_COUNTER; mTitle.section = TITLE_SECTION_1; + mTitle.nextProgSection = PROG_SECTION_LOGO; } } delete sprite; @@ -3985,7 +5035,7 @@ void GameDirector::runInstructions() // Bucle para la pantalla de game over void GameDirector::runGameOverScreen() { - while ((mGame.section == GAME_SECTION_GAMEOVER) && (mProg.section == PROG_SECTION_GAME)) + while ((mGame.section == GAME_SECTION_GAMEOVER) && (mProg.section == PROG_SECTION_GAME) && (!exit())) { // Comprueba los eventos que hay en la cola while (SDL_PollEvent(mEventHandler) != 0) @@ -4016,8 +5066,8 @@ void GameDirector::runGameOverScreen() SDL_RenderClear(mRenderer); // Dibuja los objetos - renderScoreBoard(); - mText.white->write(PLAY_AREA_CENTER_X - (mText.white->lenght("GAME OVER", 0) / 2), PLAY_AREA_CENTER_Y - BLOCK, "GAME OVER", 0); + mText.whiteX2->writeCentered(PLAY_AREA_CENTER_X, PLAY_AREA_CENTER_Y - (BLOCK * 4), "GAME OVER", 0); + mText.white->writeCentered(PLAY_AREA_CENTER_X, PLAY_AREA_CENTER_Y - (BLOCK * 1), "YOUR SCORE: " + std::to_string(mGame.player->getScore()), 0); mText.white->write(PLAY_AREA_CENTER_X - (mText.white->lenght("RETRY?", 0) / 2), PLAY_AREA_CENTER_Y + BLOCK * 2, "RETRY?", 0); mMenu.gameOver->render(); @@ -4063,43 +5113,47 @@ void GameDirector::runGameOverScreen() } } -// Dibuja la informacion de debug en pantalla -void GameDirector::renderDebugInfo() -{ - if (mProg.debug) - { - mText.white->write(0, 0 * BLOCK, "menace: " + std::to_string(mGame.menaceLevelThreshold) + "/" + std::to_string(mGame.menaceLevelCurrent)); - mText.white->write(0, 1 * BLOCK, "item[0].TTL: " + std::to_string(mGame.item[0]->mTimeToLive)); - mText.white->write(0, 2 * BLOCK, "mGame.stage: " + std::to_string(mGame.stage)); - mText.white->write(0, 3 * BLOCK, "mGame.counter : " + std::to_string(mGame.counter)); - mText.white->write(0, 4 * BLOCK, ": "); - mText.white->write(0, 5 * BLOCK, ": "); - } -} - // Activa el modo Demo void GameDirector::enableDemoMode() { mDemo.enabled = true; - //mGame.getReadyBitmap->setEnabled(false); } // Desactiva el modo Demo void GameDirector::disableDemoMode() { mDemo.enabled = false; - //mGame.getReadyBitmap->setEnabled(true); } // Actualiza el proximo estado del juego despues del titulo void GameDirector::toogleTitleNextGS() { if (mTitle.nextProgSection == PROG_SECTION_LOGO) - { mTitle.nextProgSection = PROG_SECTION_GAME; - } else - { mTitle.nextProgSection = PROG_SECTION_LOGO; +} + +// Dibuja la informacion de debug en pantalla +void GameDirector::renderDebugInfo() +{ + if (mDebug.enabled) + { + Uint8 R = 0xFF; + Uint8 G = 0x20; + Uint8 B = 0x20; + + SDL_RenderSetLogicalSize(mRenderer, SCREEN_WIDTH * 2, SCREEN_HEIGHT * 2); + + mText.white->writeShadowed(2, 2 + 0 * BLOCK, "menace(umb): " + std::to_string(mGame.menaceCurrent) + "(" + std::to_string(mGame.menaceThreshold) + ")", R, G, B); + mText.white->writeShadowed(2, 2 + 1 * BLOCK, "currentPower: " + std::to_string(mGame.stage[mGame.currentStage].currentPower), R, G, B); + mText.white->writeShadowed(2, 2 + 2 * BLOCK, "currentStage: " + std::to_string(mGame.currentStage), R, G, B); + mText.white->writeShadowed(2, 2 + 3 * BLOCK, "counter: " + std::to_string(mGame.counter), R, G, B); + mText.white->writeShadowed(2, 2 + 4 * BLOCK, "(R)enemyset: " + std::to_string(mDebug.enemySet), R, G, B); + mText.white->writeShadowed(2, 2 + 5 * BLOCK, "RGB: " + std::to_string(mDebug.gradR) + "," + std::to_string(mDebug.gradG) + "," + std::to_string(mDebug.gradB), R, G, B); + mText.white->writeShadowed(2, 2 + 6 * BLOCK, "(I)invuln : " + std::to_string(mGame.player->getInvulnerableCounter()), R, G, B); + mText.white->writeShadowed(2, 2 + 7 * BLOCK, "balloons: " + std::to_string(countBalloons()), R, G, B); + mText.white->writeShadowed(2, 2 + 8 * BLOCK, "balloonsPop: " + std::to_string(mGame.balloonsPopped), R, G, B); + mText.white->writeShadowed(2, 2 + 9 * BLOCK, "(Z-X)ballSped:" + std::to_string(mGame.enemySpeed), R, G, B); } } \ No newline at end of file diff --git a/source/gamedirector.h b/source/gamedirector.h index a096c68..42dae46 100644 --- a/source/gamedirector.h +++ b/source/gamedirector.h @@ -39,7 +39,7 @@ private: CoffeeDrop *mCoffeeDrop[MAX_COFFEE_DROPS]; // Vector con todas ls gotas de café; #endif - struct text + struct text_t { Text *white; // Texto blanco de 8x8 Text *whiteX2; // Texto blanco de 16x16 @@ -47,9 +47,9 @@ private: Text *blackX2; // Texto negro de 16x16 Text *nokia; // Texto de anchura variable y 10px de alto }; - text mText; // Variable con todos los objetos de texto + text_t mText; // Variable con todos los objetos de texto - struct menu + struct menu_t { Menu *title; // Menu de la pantalla de título Menu *pause; // Menú de la pantalla de pausa @@ -58,29 +58,70 @@ private: Menu *active; // Menu activo (de momento para la pantalla del titulo) bool keyPressed; // Variable para evitar la repetición de teclas en los menus }; - menu mMenu; // Variable con todos los objetos menus y sus variables + menu_t mMenu; // Variable con todos los objetos menus y sus variables - struct intro + struct intro_t { SmartSprite *bitmap[INTRO_TOTAL_BITMAPS]; // Vector con los sprites inteligentes para los dibujos de la intro Text2 *text[INTRO_TOTAL_TEXTS]; // Textos de la intro Uint8 events[INTRO_TOTAL_EVENTS]; // Vector para coordinar los eventos de la intro }; - intro mIntro; // Contiene todas las variables de la sección 'Intro' + intro_t mIntro; // Contiene todas las variables de la sección 'Intro' - struct game + struct enemyInits_t + { + int x; // Posición en el eje X donde crear al enemigo + int y; // Posición en el eje Y donde crear al enemigo + float velX; // Velocidad inicial en el eje X + Uint8 kind; // Tipo de enemigo + Uint16 creationCounter; // Temporizador para la creación del enemigo + }; + + struct enemyFormation_t // Contiene la información de una formación enemiga + { + Uint8 numberOfEnemies; // Cantidad de enemigos que forman la formación + enemyInits_t init[50]; // Vector con todas las inicializaciones de los enemigos de la formación + }; + enemyFormation_t mEnemyFormation[100]; // Vector con todas las formaciones enemigas + + struct enemyPool_t + { + enemyFormation_t *set[10]; // Conjunto de formaciones enemigas + }; + enemyPool_t mEnemyPool[10]; // Variable con los diferentes conjuntos de formaciones enemigas + + struct stage_t // Contiene todas las variables relacionadas con una fase + { + enemyPool_t *enemyPool; // El conjunto de formaciones enemigas de la fase + Uint16 currentPower; // Cantidad actual de poder + Uint16 powerToComplete; // Cantidad de poder que se necesita para completar la fase + Uint8 maxMenace; // Umbral máximo de amenaza de la fase + Uint8 minMenace; // Umbral mínimo de amenaza de la fase + Uint8 number; // Numero de fase + }; + + struct effect_t + { + bool flash; // Indica si se ha de pintar la pantalla de blanco + bool shake; // Indica si se ha de agitar la pantalla + Uint8 shakeCounter; // Contador para medir el tiempo que dura el efecto + }; + + struct game_t { Uint32 score; // Puntuación actual Uint32 hiScore; // Puntuación máxima Uint8 section; // Seccion actual dentro del juego bool hiScoreAchieved; // Indica si se ha superado la puntuación máxima - Uint8 stage; // Pantalla actual - Uint8 stageCounter; // Contador para el tiempo visible del texto de Stage - float stagePath[STAGE_COUNTER]; // Vector con los puntos Y por donde pasará la etiqueta + Uint8 currentStage; // Indica la fase actual + stage_t stage[10]; // Variable con los datos de cada pantalla + Uint8 stageBitmapCounter; // Contador para el tiempo visible del texto de Stage + float stageBitmapPath[STAGE_COUNTER]; // Vector con los puntos Y por donde se desplaza el texto + float getReadyBitmapPath[STAGE_COUNTER]; // Vector con los puntos X por donde se desplaza el texto Uint16 deathCounter; // Contador para la animación de muerte del jugador Uint8 deathIndex; // Indice del vector de smartsprites que contiene el sprite del jugador - Uint8 menaceLevelCurrent; // Nivel de amenaza actual - Uint8 menaceLevelThreshold; // Umbral del nivel de amenaza. Si el nivel de amenaza cae por debajo del umbral, se generan más globos. Si el umbral aumenta, aumenta el numero de globos + Uint8 menaceCurrent; // Nivel de amenaza actual + Uint8 menaceThreshold; // Umbral del nivel de amenaza. Si el nivel de amenaza cae por debajo del umbral, se generan más globos. Si el umbral aumenta, aumenta el numero de globos bool timeStopped; // Indica si el tiempo está detenido Uint16 timeStoppedCounter; // Temporizador para llevar la cuenta del tiempo detenido Uint8 remainingExplosions; // Cantidad de explosiones restantes @@ -88,26 +129,34 @@ private: bool explosionTime; // Indica si las explosiones estan en marcha Uint32 counter; // Contador para el juego Uint32 scoreDataFile[TOTAL_SCORE_DATA]; // Datos del fichero de puntos - SmartSprite *getReadyBitmap; // Sprite para el texto de GetReady del principio de la partida + Sprite *getReadyBitmap; // Sprite para el texto de GetReady del principio de la partida SmartSprite *_1000Bitmap; // Sprite con el texto 1.000 SmartSprite *_2500Bitmap; // Sprite con el texto 2.500 SmartSprite *_5000Bitmap; // Sprite con el texto 5.000 Sprite *background; // Sprite con los graficos frontales del fondo Sprite *gradient; // Sprite con los graficos del degradado de color de fondo + SDL_Rect gradientRect[4]; // Vector con las coordenadas de los 4 gradientes + Uint16 balloonsPopped; // Lleva la cuenta de los globos explotados MovingSprite *clouds1a; // Sprite para las nubes superiores MovingSprite *clouds1b; // Sprite para las nubes superiores MovingSprite *clouds2a; // Sprite para las nubes inferiores MovingSprite *clouds2b; // Sprite para las nubes inferiores Sprite *grass; // Sprite para la hierba + Sprite *scoreBoard; // Sprite para el fondo del marcador + Sprite *powerMeter; // Sprite para el medidor de poder de la fase Player *player; // El jugador Balloon *balloon[MAX_BALLOONS]; // Vector con los objetos globo Bullet *bullet[MAX_BULLETS]; // Vector con los objetos bala Item *item[MAX_ITEMS]; // Vector con los objetos item SmartSprite *smartSprite[MAX_SMART_SPRITES]; // Vector para almacenar y gestionar SmartSprites + Uint8 lastEnemyDeploy; // Guarda cual ha sido la última formación desplegada para no repetir; + Uint8 enemyDeployCounter; // Cuando se lanza una formación, se le da un valor y no sale otra hasta que llegue a cero + float enemySpeed; // Velocidad a la que se mueven los enemigos + effect_t effect; // Variable para gestionar los efectos visuales }; - game mGame; // Variable con todas las variables usadas durante el juego + game_t mGame; // Variable con todas las variables usadas durante el juego - struct title + struct title_t { Uint16 counter; // Temporizador para la pantalla de titulo Uint16 backgroundCounter; // Temporizador para el fondo de tiles de la pantalla de titulo @@ -124,19 +173,19 @@ private: AnimatedSprite *dustBitmapL; // Sprite con la el polvo que aparece al colisionar el texto de la pantalla de titulo AnimatedSprite *dustBitmapR; // Sprite con la el polvo que aparece al colisionar el texto de la pantalla de titulo }; - title mTitle; // Variable con todas las variables de la pantalla de titulo + title_t mTitle; // Variable con todas las variables de la pantalla de titulo - struct demo + struct demo_t { - bool enabled; // Indica si está activo el modo demo - bool recording; // Indica si está activado el modo para grabar la demo - Uint16 counter; // Contador para el modo demo - DemoKeys keys; // Variable con las pulsaciones de teclas del modo demo - DemoKeys dataFile[TOTAL_DEMO_DATA]; // Datos del fichero con los movimientos para la demo + bool enabled; // Indica si está activo el modo demo + bool recording; // Indica si está activado el modo para grabar la demo + Uint16 counter; // Contador para el modo demo + demoKeys_t keys; // Variable con las pulsaciones de teclas del modo demo + demoKeys_t dataFile[TOTAL_DEMO_DATA]; // Datos del fichero con los movimientos para la demo }; - demo mDemo; // Variable con todas las variables relacionadas con el modo demo + demo_t mDemo; // Variable con todas las variables relacionadas con el modo demo - struct options + struct options_t { Uint32 fullScreenMode; // Contiene el valor del modo de pantalla completa Uint32 fullScreenModePrevious; @@ -144,55 +193,63 @@ private: Uint8 windowSizePrevious; bool displayCoffeeDrops; // Indica si se han de mostar las gotas de cafe }; - options mOptions; // Variable con todas las variables de las opciones del programa + options_t mOptions; // Variable con todas las variables de las opciones del programa - struct logo + struct logo_t { Uint16 counter; // Contador Sprite *sprite; // Sprite con la textura del logo }; - logo mLogo; // Variable con las variables para el logo + logo_t mLogo; // Variable con las variables para el logo - struct prog + struct prog_t { - bool debug; // Indica si se va a mostrar la información de debug bool quit; // Indica si hay que salir del programa - Input keyboard; // Almacena los códigos de teclado correspondientes - Input keyboardBuffer; // Buffer para teclas pulsadas + input_t keyboard; // Almacena los códigos de teclado correspondientes + input_t keyboardBuffer; // Buffer para teclas pulsadas std::string executablePath; // Path del ejecutable Uint32 ticks; // Contador de ticks para ajustar la velocidad del programa Uint8 section; // Sección actual del programa; Uint8 subsection; // Subseccion dentro de la sección; Uint8 ticksSpeed; // Velocidad a la que se repiten los bucles del programa }; - prog mProg; // Contiene todas las variables globales del programa + prog_t mProg; // Contiene todas las variables globales del programa - struct resourceBinFile + struct debug_t + { + bool enabled; // Indica si se va a mostrar la información de debug + Uint8 enemySet; // Escoge el set enemigo a generar + Uint8 gradR, gradG, gradB; // Colores RGB para modificar el color del gradiente de fondo + float hudW, hudH; // Multiplica el tamaño del hud de debug; + }; + debug_t mDebug; + + struct resourceBinFile_t { std::string file; // Ruta al fichero }; - resourceBinFile mBinFile[TOTAL_BINFILE]; // Todos los ficheros binarios + resourceBinFile_t mBinFile[TOTAL_BINFILE]; // Todos los ficheros binarios - struct resourceSound + struct resourceSound_t { std::string file; // Ruta al fichero JA_Sound sound; // Variable con el sonido }; - resourceSound mSound[TOTAL_SOUND]; // Todos los sonidos + resourceSound_t mSound[TOTAL_SOUND]; // Todos los sonidos - struct resourceMusic + struct resourceMusic_t { std::string file; // Ruta al fichero JA_Music music; // Variable con la música }; - resourceMusic mMusic[TOTAL_MUSIC]; // Todas las músicas + resourceMusic_t mMusic[TOTAL_MUSIC]; // Todas las músicas - struct resourceTexture + struct resourceTexture_t { std::string file; // Ruta al fichero LTexture *texture; // Variable con la textura }; - resourceTexture mTexture[TOTAL_TEXTURE]; // Todos los gráficos + resourceTexture_t mTexture[TOTAL_TEXTURE]; // Todos los gráficos public: // Constructor @@ -294,6 +351,21 @@ public: // Establece el valor de la variable void setExecutablePath(std::string path); + // Inicializa las formaciones enemigas + void initEnemyFormations(); + + // Inicializa los conjuntos de formaciones + void initEnemyPools(); + + // Inicializa las fases del juego + void initGameStages(); + + // Crea una formación de enemigos + void deployEnemyFormation(); + + // Aumenta el poder de la fase + void increaseStageCurrentPower(); + // Establece el valor de la variable void setScore(Uint32 score); @@ -309,7 +381,10 @@ public: // Pinta el marcador en pantalla usando un objeto texto void renderScoreBoard(); - // Actualiza el valor de la variable mStage + // Actualiza las variables del jugador + void updatePlayer(); + + // Actualiza las variables de la fase void updateStage(); // Actualiza el estado de muerte @@ -318,8 +393,8 @@ public: // Renderiza el fade final cuando se acaba la partida void renderDeathFade(); - // Mueve todos los globos activos - void moveBalloons(); + // Actualiza los globos + void updateBalloons(); // Pinta en pantalla todos los globos activos void renderBalloons(); @@ -328,17 +403,38 @@ public: Uint8 getBalloonFreeIndex(); // Crea un globo nuevo en el vector de globos - Uint8 createNewBalloon(float x, int y, Uint8 kind, float velx, Uint16 stoppedcounter, LTexture *texture); + Uint8 createNewBalloon(float x, int y, Uint8 kind, float velx, float speed, Uint16 stoppedcounter, LTexture *texture); + + // Crea una PowerBall + void createPowerBall(); // Establece a cero todos los valores del vector de objetos globo void resetBalloons(); + // Establece la velocidad de los globos + void setBalloonSpeed(float speed); + + // Incrementa la velocidad de los globos + void incBalloonSpeed(); + + // Decrementa la velocidad de los globos + void decBalloonSpeed(); + + // Actualiza la velocidad de los globos en funcion del poder acumulado de la fase + void updateBalloonSpeed(); + // Explosiona un globo. Lo destruye y crea otros dos si es el caso void popBalloon(Uint8 index); + // Explosiona un globo. Lo destruye + void destroyBalloon(Uint8 index); + // Explosiona todos los globos void popAllBalloons(); + // Destruye todos los globos + void destroyAllBalloons(); + // Detiene todos los globos void stopAllBalloons(Uint16 time); @@ -393,6 +489,15 @@ public: // Crea un objeto SmartSprite void createItemScoreSprite(int x, int y, SmartSprite *sprite); + // Crea un objeto de bonus en función del azar + void dropBonus(); + + // Dibuja el efecto de flash + void renderFlashEffect(); + + // Actualiza el efecto de agitar la pantalla + void updateShakeEffect(); + // Crea un SmartSprite para arrojar el item café al recibir un impacto void throwCoffee(int x, int y); @@ -434,10 +539,10 @@ public: Uint8 getSubsection(); // Calcula y establece el valor de amenaza en funcion de los globos activos - void setMenaceLevel(); + void setMenace(); // Obtiene el valor de la variable - Uint8 getMenaceLevel(); + Uint8 getMenace(); // Establece el valor de la variable void setTimeStopped(bool value); @@ -448,6 +553,9 @@ public: // Establece el valor de la variable void setTimeStoppedCounter(Uint16 value); + // Actualiza la variable EnemyDeployCounter + void updateEnemyDeployCounter(); + // Actualiza y comprueba el valor de la variable void updateTimeStoppedCounter(); @@ -464,7 +572,10 @@ public: void updateRemainingExplosionsCounter(); // Gestiona el nivel de amenaza - void updateMenaceLevel(); + void old_updateMenace(); + + // Gestiona el nivel de amenaza + void updateMenace(); // Actualiza el campo de juego void updatePlayField(); @@ -514,6 +625,9 @@ public: // Agita la pantalla void shakeScreen(); + // Agita la pantalla + void shakeScreen2(); + // Bucle para el logo del juego void runLogo(); @@ -535,9 +649,6 @@ public: // Bucle para la pantalla de game over void runGameOverScreen(); - // Dibuja la informacion de debug en pantalla - void renderDebugInfo(); - // Activa el modo Demo void enableDemoMode(); @@ -546,6 +657,9 @@ public: // Intercambia el proximo estado del juego despues del titulo void toogleTitleNextGS(); + + // Dibuja la informacion de debug en pantalla + void renderDebugInfo(); }; #endif diff --git a/source/item.cpp b/source/item.cpp index 427620f..c6ddf86 100644 --- a/source/item.cpp +++ b/source/item.cpp @@ -251,7 +251,7 @@ void Item::setEnabled(bool value) } // Obtiene el circulo de colisión -Circle &Item::getCollider() +circle_t &Item::getCollider() { return mCollider; } diff --git a/source/item.h b/source/item.h index b554a3f..0c38a48 100644 --- a/source/item.h +++ b/source/item.h @@ -62,7 +62,7 @@ public: void setEnabled(bool value); // Obtiene el circulo de colisión - Circle &getCollider(); + circle_t &getCollider(); // Temporizador con el tiempo que el objeto está presente Uint16 mTimeToLive; @@ -93,10 +93,8 @@ private: // Especifica si está habilitado el objeto bool mEnabled; - - // Circulo de colisión del objeto - Circle mCollider; + circle_t mCollider; // Alinea el circulo de colisión con la posición del objeto void shiftColliders(); diff --git a/source/player.cpp b/source/player.cpp index 3582683..836815d 100644 --- a/source/player.cpp +++ b/source/player.cpp @@ -56,7 +56,7 @@ void Player::init(float x, int y, LTexture *textureLegs, LTexture *textureBody, mBaseSpeed = 1.5; // Establece la puntuación inicial - mScore = 9500; + mScore = 0; // Establece el multiplicador de puntos inicial mScoreMultiplier = 1.0f; @@ -100,12 +100,6 @@ void Player::init(float x, int y, LTexture *textureLegs, LTexture *textureBody, mSpriteBody->setAnimationNumFrames(PLAYER_ANIMATION_BODY_FIRING_RIGHT, 4); mSpriteBody->setAnimationNumFrames(PLAYER_ANIMATION_BODY_WALKING_STOP, 4); mSpriteBody->setAnimationNumFrames(PLAYER_ANIMATION_BODY_FIRING_UP, 4); - mSpriteBody->setAnimationNumFrames(PLAYER_ANIMATION_BODY_WALKING_LEFT_EXTRA_HIT, 4); - mSpriteBody->setAnimationNumFrames(PLAYER_ANIMATION_BODY_FIRING_LEFT_EXTRA_HIT, 4); - mSpriteBody->setAnimationNumFrames(PLAYER_ANIMATION_BODY_WALKING_RIGHT_EXTRA_HIT, 4); - mSpriteBody->setAnimationNumFrames(PLAYER_ANIMATION_BODY_FIRING_RIGHT_EXTRA_HIT, 4); - mSpriteBody->setAnimationNumFrames(PLAYER_ANIMATION_BODY_WALKING_STOP_EXTRA_HIT, 4); - mSpriteBody->setAnimationNumFrames(PLAYER_ANIMATION_BODY_FIRING_UP_EXTRA_HIT, 4); // Establece la velocidad de cada animación mSpriteLegs->setAnimationSpeed(PLAYER_ANIMATION_LEGS_WALKING_STOP, 10); @@ -118,12 +112,6 @@ void Player::init(float x, int y, LTexture *textureLegs, LTexture *textureBody, mSpriteBody->setAnimationSpeed(PLAYER_ANIMATION_BODY_FIRING_RIGHT, 5); mSpriteBody->setAnimationSpeed(PLAYER_ANIMATION_BODY_WALKING_STOP, 10); mSpriteBody->setAnimationSpeed(PLAYER_ANIMATION_BODY_FIRING_UP, 5); - mSpriteBody->setAnimationSpeed(PLAYER_ANIMATION_BODY_WALKING_LEFT_EXTRA_HIT, 5); - mSpriteBody->setAnimationSpeed(PLAYER_ANIMATION_BODY_FIRING_LEFT_EXTRA_HIT, 5); - mSpriteBody->setAnimationSpeed(PLAYER_ANIMATION_BODY_WALKING_RIGHT_EXTRA_HIT, 5); - mSpriteBody->setAnimationSpeed(PLAYER_ANIMATION_BODY_FIRING_RIGHT_EXTRA_HIT, 5); - mSpriteBody->setAnimationSpeed(PLAYER_ANIMATION_BODY_WALKING_STOP_EXTRA_HIT, 10); - mSpriteBody->setAnimationSpeed(PLAYER_ANIMATION_BODY_FIRING_UP_EXTRA_HIT, 5); // Establece si la animación se reproduce en bucle mSpriteLegs->setAnimationLoop(PLAYER_ANIMATION_LEGS_WALKING_STOP, true); @@ -136,12 +124,6 @@ void Player::init(float x, int y, LTexture *textureLegs, LTexture *textureBody, mSpriteBody->setAnimationLoop(PLAYER_ANIMATION_BODY_FIRING_RIGHT, true); mSpriteBody->setAnimationLoop(PLAYER_ANIMATION_BODY_WALKING_STOP, true); mSpriteBody->setAnimationLoop(PLAYER_ANIMATION_BODY_FIRING_UP, true); - mSpriteBody->setAnimationLoop(PLAYER_ANIMATION_BODY_WALKING_LEFT_EXTRA_HIT, true); - mSpriteBody->setAnimationLoop(PLAYER_ANIMATION_BODY_FIRING_LEFT_EXTRA_HIT, true); - mSpriteBody->setAnimationLoop(PLAYER_ANIMATION_BODY_WALKING_RIGHT_EXTRA_HIT, true); - mSpriteBody->setAnimationLoop(PLAYER_ANIMATION_BODY_FIRING_RIGHT_EXTRA_HIT, true); - mSpriteBody->setAnimationLoop(PLAYER_ANIMATION_BODY_WALKING_STOP_EXTRA_HIT, true); - mSpriteBody->setAnimationLoop(PLAYER_ANIMATION_BODY_FIRING_UP_EXTRA_HIT, true); // Establece los frames de cada animación mSpriteLegs->setAnimationFrames(PLAYER_ANIMATION_LEGS_WALKING_LEFT, 0, mWidth * 0, mHeight * 0, mWidth, mHeight); @@ -189,36 +171,6 @@ void Player::init(float x, int y, LTexture *textureLegs, LTexture *textureBody, mSpriteBody->setAnimationFrames(PLAYER_ANIMATION_BODY_FIRING_UP, 2, mWidth * 2, mHeight * 5, mWidth, mHeight); mSpriteBody->setAnimationFrames(PLAYER_ANIMATION_BODY_FIRING_UP, 3, mWidth * 3, mHeight * 5, mWidth, mHeight); - mSpriteBody->setAnimationFrames(PLAYER_ANIMATION_BODY_WALKING_LEFT_EXTRA_HIT, 0, mWidth * 0, mHeight * 6, mWidth, mHeight); - mSpriteBody->setAnimationFrames(PLAYER_ANIMATION_BODY_WALKING_LEFT_EXTRA_HIT, 1, mWidth * 1, mHeight * 6, mWidth, mHeight); - mSpriteBody->setAnimationFrames(PLAYER_ANIMATION_BODY_WALKING_LEFT_EXTRA_HIT, 2, mWidth * 2, mHeight * 6, mWidth, mHeight); - mSpriteBody->setAnimationFrames(PLAYER_ANIMATION_BODY_WALKING_LEFT_EXTRA_HIT, 3, mWidth * 3, mHeight * 6, mWidth, mHeight); - - mSpriteBody->setAnimationFrames(PLAYER_ANIMATION_BODY_FIRING_LEFT_EXTRA_HIT, 0, mWidth * 0, mHeight * 7, mWidth, mHeight); - mSpriteBody->setAnimationFrames(PLAYER_ANIMATION_BODY_FIRING_LEFT_EXTRA_HIT, 1, mWidth * 1, mHeight * 7, mWidth, mHeight); - mSpriteBody->setAnimationFrames(PLAYER_ANIMATION_BODY_FIRING_LEFT_EXTRA_HIT, 2, mWidth * 2, mHeight * 7, mWidth, mHeight); - mSpriteBody->setAnimationFrames(PLAYER_ANIMATION_BODY_FIRING_LEFT_EXTRA_HIT, 3, mWidth * 3, mHeight * 7, mWidth, mHeight); - - mSpriteBody->setAnimationFrames(PLAYER_ANIMATION_BODY_WALKING_RIGHT_EXTRA_HIT, 0, mWidth * 0, mHeight * 8, mWidth, mHeight); - mSpriteBody->setAnimationFrames(PLAYER_ANIMATION_BODY_WALKING_RIGHT_EXTRA_HIT, 1, mWidth * 1, mHeight * 8, mWidth, mHeight); - mSpriteBody->setAnimationFrames(PLAYER_ANIMATION_BODY_WALKING_RIGHT_EXTRA_HIT, 2, mWidth * 2, mHeight * 8, mWidth, mHeight); - mSpriteBody->setAnimationFrames(PLAYER_ANIMATION_BODY_WALKING_RIGHT_EXTRA_HIT, 3, mWidth * 3, mHeight * 8, mWidth, mHeight); - - mSpriteBody->setAnimationFrames(PLAYER_ANIMATION_BODY_FIRING_RIGHT_EXTRA_HIT, 0, mWidth * 0, mHeight * 9, mWidth, mHeight); - mSpriteBody->setAnimationFrames(PLAYER_ANIMATION_BODY_FIRING_RIGHT_EXTRA_HIT, 1, mWidth * 1, mHeight * 9, mWidth, mHeight); - mSpriteBody->setAnimationFrames(PLAYER_ANIMATION_BODY_FIRING_RIGHT_EXTRA_HIT, 2, mWidth * 2, mHeight * 9, mWidth, mHeight); - mSpriteBody->setAnimationFrames(PLAYER_ANIMATION_BODY_FIRING_RIGHT_EXTRA_HIT, 3, mWidth * 3, mHeight * 9, mWidth, mHeight); - - mSpriteBody->setAnimationFrames(PLAYER_ANIMATION_BODY_WALKING_STOP_EXTRA_HIT, 0, mWidth * 0, mHeight * 10, mWidth, mHeight); - mSpriteBody->setAnimationFrames(PLAYER_ANIMATION_BODY_WALKING_STOP_EXTRA_HIT, 1, mWidth * 1, mHeight * 10, mWidth, mHeight); - mSpriteBody->setAnimationFrames(PLAYER_ANIMATION_BODY_WALKING_STOP_EXTRA_HIT, 2, mWidth * 2, mHeight * 10, mWidth, mHeight); - mSpriteBody->setAnimationFrames(PLAYER_ANIMATION_BODY_WALKING_STOP_EXTRA_HIT, 3, mWidth * 3, mHeight * 10, mWidth, mHeight); - - mSpriteBody->setAnimationFrames(PLAYER_ANIMATION_BODY_FIRING_UP_EXTRA_HIT, 0, mWidth * 0, mHeight * 11, mWidth, mHeight); - mSpriteBody->setAnimationFrames(PLAYER_ANIMATION_BODY_FIRING_UP_EXTRA_HIT, 1, mWidth * 1, mHeight * 11, mWidth, mHeight); - mSpriteBody->setAnimationFrames(PLAYER_ANIMATION_BODY_FIRING_UP_EXTRA_HIT, 2, mWidth * 2, mHeight * 11, mWidth, mHeight); - mSpriteBody->setAnimationFrames(PLAYER_ANIMATION_BODY_FIRING_UP_EXTRA_HIT, 3, mWidth * 3, mHeight * 11, mWidth, mHeight); - // Selecciona un frame para pintar mSpriteLegs->setSpriteClip(mSpriteLegs->getAnimationClip(PLAYER_ANIMATION_LEGS_WALKING_STOP, 0)); mSpriteBody->setSpriteClip(mSpriteBody->getAnimationClip(PLAYER_ANIMATION_BODY_WALKING_STOP, 0)); @@ -231,71 +183,29 @@ void Player::setInput(Uint8 input) { case INPUT_LEFT: mVelX = -mBaseSpeed; - if (mExtraHit) - { - setWalkingStatus(PLAYER_STATUS_WALKING_LEFT); - } - else - { - setWalkingStatus(PLAYER_STATUS_WALKING_LEFT); - } + setWalkingStatus(PLAYER_STATUS_WALKING_LEFT); break; case INPUT_RIGHT: mVelX = mBaseSpeed; - if (mExtraHit) - { - setWalkingStatus(PLAYER_STATUS_WALKING_RIGHT); - } - else - { - setWalkingStatus(PLAYER_STATUS_WALKING_RIGHT); - } + setWalkingStatus(PLAYER_STATUS_WALKING_RIGHT); break; case INPUT_FIRE_UP: - if (mExtraHit) - { - setFiringStatus(PLAYER_STATUS_FIRING_UP); - } - else - { - setFiringStatus(PLAYER_STATUS_FIRING_UP); - } + setFiringStatus(PLAYER_STATUS_FIRING_UP); break; case INPUT_FIRE_LEFT: - if (mExtraHit) - { - setFiringStatus(PLAYER_STATUS_FIRING_LEFT); - } - else - { - setFiringStatus(PLAYER_STATUS_FIRING_LEFT); - } + setFiringStatus(PLAYER_STATUS_FIRING_LEFT); break; case INPUT_FIRE_RIGHT: - if (mExtraHit) - { - setFiringStatus(PLAYER_STATUS_FIRING_RIGHT); - } - else - { - setFiringStatus(PLAYER_STATUS_FIRING_RIGHT); - } + setFiringStatus(PLAYER_STATUS_FIRING_RIGHT); break; default: mVelX = 0; - if (mExtraHit) - { - setWalkingStatus(PLAYER_STATUS_WALKING_STOP); - } - else - { - setWalkingStatus(PLAYER_STATUS_WALKING_STOP); - } + setWalkingStatus(PLAYER_STATUS_WALKING_STOP); break; } } @@ -309,7 +219,7 @@ void Player::move() mPosX += mVelX; // Si el jugador abandona el area de juego por los laterales - if ((mPosX < PLAY_AREA_LEFT) || (mPosX + mWidth > PLAY_AREA_RIGHT)) + if ((mPosX < PLAY_AREA_LEFT - 5) || (mPosX + mWidth > PLAY_AREA_RIGHT + 5)) { // Restaura su posición mPosX -= mVelX; @@ -370,6 +280,17 @@ void Player::setFiringStatus(Uint8 status) // Establece la animación correspondiente al estado void Player::setAnimation() { + // Actualiza los frames de la animación en función del número de cafes + for (Uint8 i = 0; i < 4; i++) + { + mSpriteBody->setAnimationFrames(PLAYER_ANIMATION_BODY_WALKING_LEFT, i, mWidth * i, mHeight * (0 + (6 * mCoffees)), mWidth, mHeight); + mSpriteBody->setAnimationFrames(PLAYER_ANIMATION_BODY_FIRING_LEFT, i, mWidth * i, mHeight * (1 + (6 * mCoffees)), mWidth, mHeight); + mSpriteBody->setAnimationFrames(PLAYER_ANIMATION_BODY_WALKING_RIGHT, i, mWidth * i, mHeight * (2 + (6 * mCoffees)), mWidth, mHeight); + mSpriteBody->setAnimationFrames(PLAYER_ANIMATION_BODY_FIRING_RIGHT, i, mWidth * i, mHeight * (3 + (6 * mCoffees)), mWidth, mHeight); + mSpriteBody->setAnimationFrames(PLAYER_ANIMATION_BODY_WALKING_STOP, i, mWidth * i, mHeight * (4 + (6 * mCoffees)), mWidth, mHeight); + mSpriteBody->setAnimationFrames(PLAYER_ANIMATION_BODY_FIRING_UP, i, mWidth * i, mHeight * (5 + (6 * mCoffees)), mWidth, mHeight); + } + switch (mStatusWalking) { case PLAYER_STATUS_WALKING_LEFT: @@ -377,51 +298,19 @@ void Player::setAnimation() switch (mStatusFiring) { case PLAYER_STATUS_FIRING_UP: - if (mExtraHit) - { - mSpriteBody->animate(PLAYER_ANIMATION_BODY_FIRING_UP_EXTRA_HIT); - } - else - { - mSpriteBody->animate(PLAYER_ANIMATION_BODY_FIRING_UP); - } - + mSpriteBody->animate(PLAYER_ANIMATION_BODY_FIRING_UP); break; case PLAYER_STATUS_FIRING_LEFT: - if (mExtraHit) - { - mSpriteBody->animate(PLAYER_ANIMATION_BODY_FIRING_LEFT_EXTRA_HIT); - } - else - { - mSpriteBody->animate(PLAYER_ANIMATION_BODY_FIRING_LEFT); - } - + mSpriteBody->animate(PLAYER_ANIMATION_BODY_FIRING_LEFT); break; case PLAYER_STATUS_FIRING_RIGHT: - if (mExtraHit) - { - mSpriteBody->animate(PLAYER_ANIMATION_BODY_FIRING_RIGHT_EXTRA_HIT); - } - else - { - mSpriteBody->animate(PLAYER_ANIMATION_BODY_FIRING_RIGHT); - } - + mSpriteBody->animate(PLAYER_ANIMATION_BODY_FIRING_RIGHT); break; case PLAYER_STATUS_FIRING_NO: - if (mExtraHit) - { - mSpriteBody->animate(PLAYER_ANIMATION_BODY_WALKING_LEFT_EXTRA_HIT); - } - else - { - mSpriteBody->animate(PLAYER_ANIMATION_BODY_WALKING_LEFT); - } - + mSpriteBody->animate(PLAYER_ANIMATION_BODY_WALKING_LEFT); break; default: @@ -435,51 +324,19 @@ void Player::setAnimation() switch (mStatusFiring) { case PLAYER_STATUS_FIRING_UP: - if (mExtraHit) - { - mSpriteBody->animate(PLAYER_ANIMATION_BODY_FIRING_UP_EXTRA_HIT); - } - else - { - mSpriteBody->animate(PLAYER_ANIMATION_BODY_FIRING_UP); - } - + mSpriteBody->animate(PLAYER_ANIMATION_BODY_FIRING_UP); break; case PLAYER_STATUS_FIRING_LEFT: - if (mExtraHit) - { - mSpriteBody->animate(PLAYER_ANIMATION_BODY_FIRING_LEFT_EXTRA_HIT); - } - else - { - mSpriteBody->animate(PLAYER_ANIMATION_BODY_FIRING_LEFT); - } - + mSpriteBody->animate(PLAYER_ANIMATION_BODY_FIRING_LEFT); break; case PLAYER_STATUS_FIRING_RIGHT: - if (mExtraHit) - { - mSpriteBody->animate(PLAYER_ANIMATION_BODY_FIRING_RIGHT_EXTRA_HIT); - } - else - { - mSpriteBody->animate(PLAYER_ANIMATION_BODY_FIRING_RIGHT); - } - + mSpriteBody->animate(PLAYER_ANIMATION_BODY_FIRING_RIGHT); break; case PLAYER_STATUS_FIRING_NO: - if (mExtraHit) - { - mSpriteBody->animate(PLAYER_ANIMATION_BODY_WALKING_RIGHT_EXTRA_HIT); - } - else - { - mSpriteBody->animate(PLAYER_ANIMATION_BODY_WALKING_RIGHT); - } - + mSpriteBody->animate(PLAYER_ANIMATION_BODY_WALKING_RIGHT); break; default: @@ -493,51 +350,19 @@ void Player::setAnimation() switch (mStatusFiring) { case PLAYER_STATUS_FIRING_UP: - if (mExtraHit) - { - mSpriteBody->animate(PLAYER_ANIMATION_BODY_FIRING_UP_EXTRA_HIT); - } - else - { - mSpriteBody->animate(PLAYER_ANIMATION_BODY_FIRING_UP); - } - + mSpriteBody->animate(PLAYER_ANIMATION_BODY_FIRING_UP); break; case PLAYER_STATUS_FIRING_LEFT: - if (mExtraHit) - { - mSpriteBody->animate(PLAYER_ANIMATION_BODY_FIRING_LEFT_EXTRA_HIT); - } - else - { - mSpriteBody->animate(PLAYER_ANIMATION_BODY_FIRING_LEFT); - } - + mSpriteBody->animate(PLAYER_ANIMATION_BODY_FIRING_LEFT); break; case PLAYER_STATUS_FIRING_RIGHT: - if (mExtraHit) - { - mSpriteBody->animate(PLAYER_ANIMATION_BODY_FIRING_RIGHT_EXTRA_HIT); - } - else - { - mSpriteBody->animate(PLAYER_ANIMATION_BODY_FIRING_RIGHT); - } - + mSpriteBody->animate(PLAYER_ANIMATION_BODY_FIRING_RIGHT); break; case PLAYER_STATUS_FIRING_NO: - if (mExtraHit) - { - mSpriteBody->animate(PLAYER_ANIMATION_BODY_WALKING_STOP_EXTRA_HIT); - } - else - { - mSpriteBody->animate(PLAYER_ANIMATION_BODY_WALKING_STOP); - } - + mSpriteBody->animate(PLAYER_ANIMATION_BODY_WALKING_STOP); break; default: @@ -616,7 +441,7 @@ void Player::update() setAnimation(); shiftColliders(); updateCooldown(); - updateInvulnerableTimer(); + updateInvulnerableCounter(); } // Obtiene la puntuación del jugador @@ -692,19 +517,19 @@ void Player::setInvulnerable(bool value) } // Obtiene el valor de la variable -bool Player::getInvulnerableTimer() +Uint16 Player::getInvulnerableCounter() { return mInvulnerableCounter; } // Establece el valor de la variable -void Player::setInvulnerableTimer(Uint16 value) +void Player::setInvulnerableCounter(Uint16 value) { mInvulnerableCounter = value; } // Actualiza el valor de la variable -void Player::updateInvulnerableTimer() +void Player::updateInvulnerableCounter() { if (mInvulnerableCounter > 0) { @@ -762,7 +587,7 @@ Uint8 Player::getCoffees() } // Obtiene el circulo de colisión -Circle &Player::getCollider() +circle_t &Player::getCollider() { return mCollider; } diff --git a/source/player.h b/source/player.h index 60ad831..8681590 100644 --- a/source/player.h +++ b/source/player.h @@ -38,7 +38,7 @@ private: AnimatedSprite *mSpriteBody; // Sprite para dibujar el cuerpo AnimatedSprite *mSpriteHead; // Sprite para dibujar la cabeza - Circle mCollider; // Circulo de colisión del jugador + circle_t mCollider; // Circulo de colisión del jugador void shiftColliders(); // Actualiza el circulo de colisión a la posición del jugador public: @@ -125,13 +125,13 @@ public: void setInvulnerable(bool value); // Obtiene el valor de la variable - bool getInvulnerableTimer(); + Uint16 getInvulnerableCounter(); // Establece el valor de la variable - void setInvulnerableTimer(Uint16 value); + void setInvulnerableCounter(Uint16 value); // Actualiza el valor de la variable - void updateInvulnerableTimer(); + void updateInvulnerableCounter(); // Obtiene el valor de la variable bool hasExtraHit(); @@ -152,7 +152,7 @@ public: Uint8 getCoffees(); // Obtiene el circulo de colisión - Circle &getCollider(); + circle_t &getCollider(); }; #endif diff --git a/source/sprite.cpp b/source/sprite.cpp index 23df471..dd4344f 100644 --- a/source/sprite.cpp +++ b/source/sprite.cpp @@ -16,14 +16,14 @@ Sprite::~Sprite() // Inicializador void Sprite::init(int x, int y, int w, int h, LTexture *texture, SDL_Renderer *renderer) { - // Establece el alto y el ancho del sprite - setWidth(w); - setHeight(h); - // Establece la posición X,Y del sprite setPosX(x); setPosY(y); + // Establece el alto y el ancho del sprite + setWidth(w); + setHeight(h); + // Establece el puntero al renderizador de la ventana setRenderer(renderer); diff --git a/source/text.cpp b/source/text.cpp index dbc8ba8..b3545c7 100644 --- a/source/text.cpp +++ b/source/text.cpp @@ -191,6 +191,15 @@ void Text::writeColored(int x, int y, std::string text, Uint8 R, Uint8 G, Uint8 mSprite->getTexture()->setColor(255, 255, 255); } +// Escribe el texto con sombra +void Text::writeShadowed(int x, int y, std::string text, Uint8 R, Uint8 G, Uint8 B) +{ + mSprite->getTexture()->setColor(R, G, B); + write(x+1, y+1, text); + mSprite->getTexture()->setColor(255, 255, 255); + write(x, y, text); +} + // Escribe el texto centrado en un punto x y con kerning void Text::writeCentered(int x, int y, std::string text, int kerning) { diff --git a/source/text.h b/source/text.h index 8f6f86b..1a5950b 100644 --- a/source/text.h +++ b/source/text.h @@ -39,6 +39,9 @@ public: // Escribe el texto con colores void writeColored(int x, int y, std::string text, Uint8 R, Uint8 G, Uint8 B); + // Escribe el texto con sombra + void writeShadowed(int x, int y, std::string text, Uint8 R, Uint8 G, Uint8 B); + // Escribe el texto centrado en un punto x y con kerning void writeCentered(int x, int y, std::string text, int kerning = 0); diff --git a/source/utils.cpp b/source/utils.cpp index ca82b32..c6cb741 100644 --- a/source/utils.cpp +++ b/source/utils.cpp @@ -9,7 +9,7 @@ double distanceSquared(int x1, int y1, int x2, int y2) } // Detector de colisiones entre dos circulos -bool checkCollision(Circle &a, Circle &b) +bool checkCollision(circle_t &a, circle_t &b) { // Calcula el radio total al cuadrado int totalRadiusSquared = a.r + b.r; @@ -27,7 +27,7 @@ bool checkCollision(Circle &a, Circle &b) } // Detector de colisiones entre un circulo y un rectangulo -bool checkCollision(Circle &a, SDL_Rect &b) +bool checkCollision(circle_t &a, SDL_Rect &b) { //Closest point on collision box int cX, cY; @@ -60,10 +60,10 @@ bool checkCollision(Circle &a, SDL_Rect &b) cY = a.y; } - //If the closest point is inside the circle + //If the closest point is inside the circle_t if (distanceSquared(a.x, a.y, cX, cY) < a.r * a.r) { - //This box and the circle have collided + //This box and the circle_t have collided return true; } diff --git a/source/utils.h b/source/utils.h index 2efa4e9..8b188eb 100644 --- a/source/utils.h +++ b/source/utils.h @@ -5,7 +5,7 @@ #define UTILS_H // Estructura para definir un circulo -struct Circle +struct circle_t { Uint16 x; Uint16 y; @@ -13,7 +13,7 @@ struct Circle }; // Estructura para definir todas las entradas que aceptará el programa -struct Input +struct input_t { Uint8 up; Uint8 down; @@ -29,7 +29,7 @@ struct Input }; // Estructura para mapear el teclado usado en la demo -struct DemoKeys +struct demoKeys_t { Uint8 left; Uint8 right; @@ -43,15 +43,12 @@ struct DemoKeys double distanceSquared(int x1, int y1, int x2, int y2); // Detector de colisiones entre dos circulos -bool checkCollision(Circle &a, Circle &b); +bool checkCollision(circle_t &a, circle_t &b); // Detector de colisiones entre un circulo y un rectangulo -bool checkCollision(Circle &a, SDL_Rect &b); +bool checkCollision(circle_t &a, SDL_Rect &b); // Detector de colisiones entre un dos rectangulos bool checkCollision(SDL_Rect a, SDL_Rect b); -// Inicializa el vector con los valores del seno -void initSin(); - #endif \ No newline at end of file