From a8bc01bedd1dea35e3bfe15d40922ab918521678 Mon Sep 17 00:00:00 2001 From: Trevor Boddy Date: Wed, 18 Mar 2026 13:23:51 -0400 Subject: [PATCH] shit --- build.sh | 5 +- compile.sh | 2 +- default.profraw | Bin 0 -> 13768 bytes res/bullets.png | Bin 8340 -> 8340 bytes res/door.png | Bin 0 -> 4940 bytes res/enemies/boss1.png | Bin 0 -> 3351 bytes res/enemies/boss2.png | Bin 0 -> 3166 bytes res/enemies/boss3.png | Bin 0 -> 3418 bytes res/enemies/boss4.png | Bin 0 -> 3468 bytes res/eyebig.png | Bin 0 -> 2905 bytes res/font.png | Bin 3213 -> 3490 bytes res/fontbig.png | Bin 2143 -> 2157 bytes res/fontbigger.png | Bin 0 -> 2244 bytes res/ground.png | Bin 5646 -> 5412 bytes res/life.png | Bin 1889 -> 1891 bytes res/life2.png | Bin 1917 -> 1918 bytes res/mapindicator.png | Bin 1942 -> 2043 bytes res/musicroom.png | Bin 0 -> 4333 bytes res/resources.res | 41 +- res/sfx/beatgame.wav | Bin 0 -> 23506 bytes res/sfx/beatlevel.wav | Bin 0 -> 8096 bytes res/sfx/bullet1.wav | Bin 0 -> 740 bytes res/sfx/bullet2.wav | Bin 0 -> 1800 bytes res/sfx/bullet3.wav | Bin 0 -> 1810 bytes res/sfx/explosion1.wav | Bin 0 -> 4304 bytes res/sfx/explosion2.wav | Bin 0 -> 10020 bytes res/sfx/gameover.wav | Bin 0 -> 17928 bytes res/sfx/menuchoose.wav | Bin 0 -> 5741 bytes res/sfx/menuselect.wav | Bin 0 -> 1471 bytes res/sfx/playerhit.wav | Bin 0 -> 9500 bytes res/sfx/playershot.wav | Bin 0 -> 2491 bytes res/sfx/startgame.wav | Bin 0 -> 8068 bytes res/sky.png | Bin 6028 -> 5323 bytes res/skyred.png | Bin 1436 -> 1643 bytes res/skytop.png | Bin 5509 -> 5123 bytes res/stars.png | Bin 0 -> 4606 bytes res/start/bg1.png | Bin 0 -> 6944 bytes res/start/bg2.png | Bin 0 -> 4522 bytes res/start/bg3.png | Bin 0 -> 6157 bytes res/start/bg4.png | Bin 0 -> 4391 bytes res/start/bg5.png | Bin 0 -> 4714 bytes res/start/bg6.png | Bin 0 -> 3242 bytes res/start/logo.png | Bin 6208 -> 2583 bytes res/start/splash1.png | Bin 6487 -> 8615 bytes run.sh | 3 +- src/background.h | 95 +++- src/boot/sega.s | 120 ----- src/bullets.h | 179 ++++--- src/chrome.h | 234 ++++++--- src/enemies.h | 92 ++-- src/enemytypes.h | 1126 +++++++++++++++++++++++++++------------- src/global.h | 143 +++-- src/main.c | 81 ++- src/player.h | 103 +++- src/sfx.h | 125 ++--- src/stage.h | 298 ++++++----- src/starfield.h | 107 ++++ src/start.h | 317 +++++++++-- src/treasure.h | 36 +- 59 files changed, 2053 insertions(+), 1054 deletions(-) create mode 100644 default.profraw create mode 100644 res/door.png create mode 100644 res/enemies/boss1.png create mode 100644 res/enemies/boss2.png create mode 100644 res/enemies/boss3.png create mode 100644 res/enemies/boss4.png create mode 100644 res/eyebig.png create mode 100644 res/fontbigger.png create mode 100644 res/musicroom.png create mode 100644 res/sfx/beatgame.wav create mode 100644 res/sfx/beatlevel.wav create mode 100644 res/sfx/bullet1.wav create mode 100644 res/sfx/bullet2.wav create mode 100644 res/sfx/bullet3.wav create mode 100644 res/sfx/explosion1.wav create mode 100644 res/sfx/explosion2.wav create mode 100644 res/sfx/gameover.wav create mode 100644 res/sfx/menuchoose.wav create mode 100644 res/sfx/menuselect.wav create mode 100644 res/sfx/playerhit.wav create mode 100644 res/sfx/playershot.wav create mode 100644 res/sfx/startgame.wav create mode 100644 res/stars.png create mode 100644 res/start/bg1.png create mode 100644 res/start/bg2.png create mode 100644 res/start/bg3.png create mode 100644 res/start/bg4.png create mode 100644 res/start/bg5.png create mode 100644 res/start/bg6.png create mode 100644 src/starfield.h diff --git a/build.sh b/build.sh index 41b0d7a..faabf93 100755 --- a/build.sh +++ b/build.sh @@ -2,5 +2,6 @@ rm -rf res/resources.o res/resources.h out/* # make # ./blastem/blastem out.bin #dgen out.bin -docker run --rm -v $PWD:/m68k -t registry.gitlab.com/doragasu/docker-sgdk:v2.00 -/Applications/Genesis\ Plus.app/Contents/MacOS/Genesis\ Plus out/rom.bin \ No newline at end of file +docker run --rm -v $PWD:/m68k -t registry.gitlab.com/doragasu/docker-sgdk:v2.11 +# /Applications/Genesis\ Plus.app/Contents/MacOS/Genesis\ Plus out/rom.bin +/Applications/ares.app/Contents/MacOS/ares out/rom.bin --system "Mega Drive" \ No newline at end of file diff --git a/compile.sh b/compile.sh index 86704ed..fada6ac 100755 --- a/compile.sh +++ b/compile.sh @@ -1,2 +1,2 @@ rm -rf res/resources.o res/resources.h out/* -docker run --rm -v $PWD:/m68k -t registry.gitlab.com/doragasu/docker-sgdk:v2.00 \ No newline at end of file +docker run --rm -v $PWD:/m68k -t registry.gitlab.com/doragasu/docker-sgdk:v2.11 \ No newline at end of file diff --git a/default.profraw b/default.profraw new file mode 100644 index 0000000000000000000000000000000000000000..c1a70fd7b4ed920acfeb201e9e48dfc3d6170e1b GIT binary patch literal 13768 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 OWiVRiIQGC7%KrmT^Li-& literal 0 HcmV?d00001 diff --git a/res/bullets.png b/res/bullets.png index b234322fc278f82ead07bc1319f491df9a3d40e4..de02bb290071fd6f258f641712959a91b99f0215 100644 GIT binary patch delta 169 zcmbQ@IK^=TD>Ju|L5QJ&m9eRnvE^g|W+fE9Epvd1g;`3fv4xSTu7QDpxvoiavZ-z& z5a^~^rluO1Tc)I$n5S;u$XvnXN{mV+h2j#|vJ$KEjMO|^B_jhPGhG8CU1T@RXVsfL RhpnDv!J&f4&GPJ}k^rAbE?WQq delta 169 zcmbQ@IK^=TD>JvDafqRrm4T_1k-=mEW+fE9Epvd1sY#-Rv4xSLu0g7qnXXBaS(SJR8lA|aV;ycD$hvGvsE%OFf!9MFw#YK!+ch~ T$#dB1SyXQs+}bS9UMdLy6)G&A diff --git a/res/door.png b/res/door.png new file mode 100644 index 0000000000000000000000000000000000000000..975adf77ddb035fa7066ab1062acf0792b907ad6 GIT binary patch literal 4940 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 D%Fm`D literal 0 HcmV?d00001 diff --git a/res/enemies/boss1.png b/res/enemies/boss1.png new file mode 100644 index 0000000000000000000000000000000000000000..ba1f6239b14af664ef3ba377aa62dabb0f9f2317 GIT binary patch literal 3351 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 literal 0 HcmV?d00001 diff --git a/res/enemies/boss3.png b/res/enemies/boss3.png new file mode 100644 index 0000000000000000000000000000000000000000..587fa35a9bee11c8f1f9070de06e86fee8a1b585 GIT binary patch literal 3418 zcmb_f2~-o;8jeC)L_FV!LqR>{v$z-xXl1Y;w38Fy3?I=$k z6tq%yEutuW;>H8RW<{!2P(T4eDehpQDgtHe1dwNYY+p~$n{zUEneTr8_b>O}lfA)# zK86d-7vOL>LqA{75UkyT{nGTbuy;*M?h>p4g9F%JSi9WV#=Fd`plzp@vvMnUGMQwX zTleH{==z>iRa?cjr*{fgh}Pdiu9O|G7<$z=f2~MErZ4sVM2^E5U_1s(=HU*1ZHB|? zjz$!C4#F2on1q4KY64!!WfH=uERZGj zKm-$X8G@(VlK>b5DR@V^J)K4eAty&3Y={9=;iNG#(X??c zOhiP87?CLC7?w1Ll?qS=Di@%CKt1>QHwLiivRHFA-l;__o--j=cx}bpm<`A~(Q>v* ziV#B(IT|B_5wES7OlyrdQig{NffT5WjiRDAxe9(G8BeC!JK=3u5G<5voUnas3E~MU z5GFyBH$am#5}QP3P$&$L4uB2}5S)RsP_B@tdK>BpkmziZ1A{_lfKI=GVs*oX6wvo!;s@WY9ihMrX6AAItRl$H@O|>%# zuqMq2moPgmd`CR~lI7X;u>u4m{gdR+SeK(bMJyyk-1u1R{z7>X|46Xex7b@LDKWXT|e~NEZi08xNPs4#T8SX54L!j2#0aLBP{&W{^pSk z2A~qb?(>xkl@Qnwvf1MCQ_p1EYTEZ&0piq-vydhG=MJ@IB=<;mlLKFnChZ>mk_oAM z&bQv)Jo&ylT&Lz-U8j`#FKlpr-^rVy>kE0pFI_4fhbr3}2TW?n_+w@U-Jar<|BTir z?OSu;WH+Ox*u?cwQIucdhqZb}i9&ntvu(6X)rHZShdQlWuKFypD>|gg(*D$B_~&l7 zS1l{slmlV3#r5mzulK8tUdU;qUyD*VRDbPrx__+DB;tHi!GRA_ztfv~@o_q0Ri3R9 zEyp!7e+10;nRNT$+_Hd#)IMFMbzfKnJ9h$g5?9W{ zaWZh%4g&gzYjH}56_JT8xNO0)UgY9@BHFD ze933gG9#;#x*17Xplee&&dk^G)OW#(7iDa%kog@#yQai1n>LJHj6Y{7z?pYY`y)I0 zYN>gpx{+G0^|%mQeKSG+AvbvZL<^W`(6xPo;aJZ2+E%@IVvtM=aXM+PW$ z4U6h7ny5p)k38Qw(IiPv2ywoso49Mg*f(PP_c|jFDp$Gs6g}g8=coP814kb0`-HoH zVNQD5We2`+_0$@Nl=Q^;OYs{s>R)pMz3b!p1v-}C89@t zCf0eG8}Ga2M~xaQ*^wJ!XqV_=l=_Tbon5@C*H^5(ep$cZU~xcw?3#@w{55f-PGgeQ z{kks1#ZF{?iQ~6hnUJe-;D0i|c*b}!W$SBFG zjHFdFzH4Ndsdb3DeI)efw3kZLc(ujgjic*fTRkm(8x`4Ke}J%e)b{bHM;%gma^msx zNWB3St<_9s`Cg=VO?EiW)vcehso?$kReATSKw|#NrOOO)21nejSIZjt9@^)|yVO~k z+TER|yzDK@DT9*KW_qM(bkl)Lh1$zZk0;!7ki~|@R}?=m#Nh*!8~jrru5?t^h7ktM z$Fgq@iBA^~RDBfLxZ%!IgMfRDW&(ksu<2YvW7xSaQN5KLEsr#?)0;bCJBe<5FqPRHSMd2*(U5z+rB2_@UC)hf8yIdgJ7sD<3~nSkh!Vb4 zKiRe0TU|g5YJ#kW`pd5d+&gbIf%cnT_dhmS`s%BZD%WO)i+RD=kXLV==b2?itUmOo ze-8(rFpc9)xovO00A^A$AMLhQl&b~o*KsTIc6N3OQ(qJX^;dqpr{T(W%h3#e^dhUT zzG}bDHqBVyF&KC^tyHb&x|x`ATK#0B_S%XMtWI9qayV@MK+y7AzYGcuEbGjgpE*wi zzD$}ZQv2q#8kBSfW&cBa$B2jVPxb-pEa+MRw_td!i_{n?3sIdZzOu&SMabeO#oMw@tB<9PQI1J#vhDQSPZ7;y?nf<^ zH{WO*cR)Wqp*$Ye9<%w|X9KNvr+K+jX|TfX=e$IDuB literal 0 HcmV?d00001 diff --git a/res/enemies/boss4.png b/res/enemies/boss4.png new file mode 100644 index 0000000000000000000000000000000000000000..fd3e0a502c29e1a92eb1797f531378857ed398bd GIT binary patch literal 3468 zcmb_f2~-o;8Xk(E7&)Typjw3xDrIpp*&&fdg<(y<@?tJ(AzyJTgd+*6E zU+=YsbC=8o0Km|Lv0RA=Nsg3IM1*+~E=r6ONij44IJ(41VI&Gw;JIjoKqD8E9$3wTb1jdU-P zm(&%F6mSw`Xh4E@Ad(P;FnI(QXS`z^3nLJr3K${-A~W#Q z9|9Io#^ba6-Q1_cVNXtkNQFYmf}q&gSYj-dD3L`#WG0gdkth&_0%8bI9xqnFaiCak z`G&y_l_N5NR3VUv@mfZhD~VA!5iqf@vk*yVXvOmBN?|pF;$SI6CX%#SO#ykx3{Dy& z6HW!^ArLA=MW|RI$FSrXtTa-hkjNt?e?mR;`gZ}a>Uw$2#CWF`k!U7_T*2Oqr7>NQ zccSHi@lq7>N9B?j8G^DmV=^tZ*+^NgG89%wWPuWi@J+9L-$=$&7(@rWwHJ&C#M&fm z-kO5C!3xxgpzRx|Z5la{Okq)}ED{qW(O4wXYp9omC*a4w4Ydc!%s?`YMWwJv)Zald z-|%1s{6E1wgvFQ0L@<`JKmTmg#_)Wgk* zfMrb-2zV?q6^5xaOfL_mF+m#Do(XbE2nD1uD0~`^LE}20%s2DhBuI?5x7zuyoz9aW z7{`C3!uDJT80IlRl*Z?RFrP*SVLF8gA_$YlWI8yYr~~?j%||A{78@*ln^midhcQy@ z$vir0k1Y}|N&;!9Jr(5g7$lIyB+*f9oJ8aEwSJzm2#X_-W6g`7ULgT!^z@xjfS+;& z3r4hVcOoF#HlsYk^swL^_4r$nr{~8;q8RC4H2-yUxrDEXg=OgK2&{I0!(Pyz$(O^M z|5`kQM`w~~2n0}bZM_@9U|0elAh_??UGqiRj&p7v=iJvYF z5($eVP;3W<2>(2tzvs=@I^Ry`ovZ8rnGU4gH(&b<`lAJ>-o3TQV$GZy!ZvR0?aZNp zJX-{+SpDl8jwN6DpS{&jNk82o&4;)-ZG8vCLpS%>cv=F zRYZz4J&W0V{iBKP#Mt2umS>Zozdpz3|$Hk-TKTz|B>wVA2EENRGC^vNro z!H)QdhLs^p(wb}%hm1!DgK@34Tbol6&z$6jjqjr&$#-ib^o6&Jg#xPv#?ZIUeckCf z`XRa3T0c9`SvMav^aOzO7gb|JpGFx0w}XJaBp=D+s}cEg^}ZJxEI9~->&;u*8T89T zb#=dH_l|S-OcKT~>0Q#fwd!jl4K)%d*_c7hmxcP6<#wGKL_}xTCTp75LIb-!JDA4v zXeN6fb7F^U{m+e5NbU>I^`^R~3YrdA9ZmiI+Y6RU9Xm2-dnb{<%9Aw&W>Z1$3$aI> zCrWbPYd>k{u4}&G4(aCVpOVUu{<>J$s$oxa38)x4q~o&bBZH#g6B=T^ts$_){pBG+ z)vs4tN6W|i0*@b&S&bI%i0<38XQeuUu8K3!dFX?;kMf)EbHP6-%qrYa!|u(~_}W$H zL4wW$lbJ4yw-^>IORZ7|POO~}Rvulm;Y{1EzEH6`C#~;{+E1*GJa({29sFH%SoQqO zIWIRCw_~~MQ=B#KFPxUscL7uajf8^>E1H;r#3MouEJ9z5j_16*_IvpOM~^d zUJZ#!$Qs^hW;q+mtcgCU_;E6|=Gu400}Gm>pVph340UXMELCbWhF^4f_umhgP;%Vb zGi0HsEuJVXDK1?Su5;&bb9>xws#AAEnQ6pJAGHiD_-)hZHkYpO{DQ;LUk$lBep7SM zBdFFu5p#O~(A>(O$`_M!^Tv`cp5o7PtDnCXOv>ArP}&fhJ*W#5f1EMd)6J8{oHiLY zro|mjV+YGFCEJEo7u~oPoH8)0-C#?*OLk|2ys&ZZK=~r6xlZO#R(p9>y3Vuq!`OJh z*R%I`kxO@beTZ9_0<`MKcCHM$n7O|5Jp-S?(0_$=03v>6`Yd8H)xzh{Pmhbfy9wl! zbF#K=1d0<=QjqH5n#5T(O%C5b*jMg3ZqXchIBhWwxC~S}N=nX&^6uK2#X71SO>R~$ zOkfoL(Ar-xtUHj{_j$n$^ZgsIvCwr}%xh0*Li3H*MtRucXyLR%-&DpRch$;&m-}&PYW~z+wB&!1JXFd0SRhAjKFluvc9@*UCEU4&>Yq=e8 z`I`i*=R=CfTl3DO=(OAkJ`p5%5x;H;E&bducx2ejS(*K_Bf5l*i%HU)&;8hhr+E}? zIexF31FiE{#;$Q5lXjbLH=4bG-Z5T3{vh4Cr0(d=Fs1t|)N$d)=c@{X?fflze428% zO4$01xI(CIoe22`Q3ZiV)R@R_clS+oPh)c9J?fJ&&G9>s z|Ag{6Cvn58^BR8A^K@0i@b(Jpu&l7V%YBA3s=GRMdqm+0mp^&gPsOp%^hT=-Gz6ZL z|73~~=+HgeQTHHGwNQ7(O=#f=Yc= S3Hvt$03K{_x3g=)KmQLu`+xHQ literal 0 HcmV?d00001 diff --git a/res/eyebig.png b/res/eyebig.png new file mode 100644 index 0000000000000000000000000000000000000000..ac8ed4154da350fb5d02cdf403bfaa66e667aa8e GIT binary patch literal 2905 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 delta 1573 zcma)(Yc$gf0Kmt}Qn6KZLq+HH}%8d}QRk_q#E zB$74nM_%bt7S&R*Sy38y_uP+tyB~k&{LcA(_?4=qt7IJotH5-#jgZ@9^)LC<1w4}7 zg|aw}!djxxmZlaA7#Q&zTZ6r{(Z(cn2pSV&5JWP^8kh!~8XFKzQCI^MmK1~vCYqv6 z{=_!afkS2gm&ve|+y7gFAXg1!nl<-;K-)R zx05<<@DanYW|to3c);CJ1ERFY)z>9W-x!I)40A_Wd*UpLW#fVCwvIkohBcSlB^7b z9w<${23*)BYtlhU_K(!dRcmWLbK(8dkB@sOqYu@bN+RA5t742NVqbPry88nHj8?-6 zvCyln(N@1>ic`zs;su#e;WqTMW(Q=vt}ul zokB#M+uFztRb@7glX@v`)J?E9kqw44pcUKl+OtPA7{a-z=Gg_0^>!vVpW zx;ImHPfS!BBd%R)aJrn2$#NVDW(ICbIv&Km=-fThqbDS!*^8W2w}D0wwIBI9NCAkO z9`~6eq#iDAFMl`pj!PC$$0O&+v*N`(xUyP?C)IbeLTU%kV(v(MZa&t~*$;WV@7i4E z)5GB*bVq=bPgKZYfM;CF3ZcAcxO3zKY0e4HsZV~2^6^29JG3A}iC)aTS%t98j#};?7K{#pnppVei2IBvo=K-GqPx3Y60+?k#&TEB%5)8RFz8S{8;l)Q2Og8G1?51r0{KjtnZoRow-d5cu7kCwebq_nDAETNy$Lk$_HngPl*hf$){0E3T zsXi_9BU>mkcQYqTboz`oG|Q7enNLE%yDm>@Td&@SPxRhd81E>(nYu0Hmk(5FwvtVsdC1~Yi&xf(UU{()*zqQqYf}@rs%@$J>k>IymyL}9_1)*@yyJEwrueC%XKP}w za+N5KRzY!9E25JX`?DUdo3`_ON610jmwNoI(6Cp>_%ca@5HwGm)h3X^i2mCcPe5AU zJmA!DuSPG8YXq+Tx@5w6z91?Lua)1rN;oZwlqzODiv5L#bs1erpSE|%)C6RJlaHjD UW^^`a7YGD$arAJgIZn9#FG3gk7ytkO diff --git a/res/fontbig.png b/res/fontbig.png index d394a915104407913ce5c2fddef899b26cf0d41f..a9f4c3f6e300253b6187513ec2901fecb1ae48d2 100644 GIT binary patch delta 497 zcmcaF@K#_0D>Ju|L5Pu&m8pr9f$3xcW+fE9Epvd1X=+M}rC~~vZfc^Lxvq(+Nt&)@ zlA)=tse!RknsHL9Ns5ud=8eo*Ohl=iY|kc!V#P#Oy~%xS_4TX6_I_btV07_xaSZY3 zd>e8)uf>2zM(*xmx2FI9eb%V5Pk3~y&w10rkh6K!uVo5s>W*!E!EojP(^M^=`CogM zCWNkko4RM`wguBIF6B)7c){_@8}ErL-df)Kaf~fZz&j-{zO+G7qdGy+@0-m0A8I|5 zI3~?7Sn9OQ+iYFihpD!&>Lbm}1p?Vu$Jriw=pz2DT*^bC+HdcJDPjRBdvgjIOwM|L zsptILndrB8s_N-{uT9OFKYONa`t{+6ey&0TWAcr_8ipMLau;{;CW@OyZhN?`KQUPI zUvx=5kNj?_{DU(0XEMk<_&Iy;iVF7gaZNX>|4UmltbV`YS>Jkzm?s`jJW2;}ZY9V~pD>JvDS%{&Dm9eFjiOFOEW+fE9Epvd1nVFG^L8?)ru1RvLv93v)MVf9> zvPrUTQle3+p-HkyQnH1`=8eo*Ohl=iY|kc!V#P#Oy~%xS_4ONmH=So-U^Me|aSZY3 zd>ZsN@2~;Ko(RuB|N5obJ6|#!U$@z>`1W6^pr-df4A>g}>v1>mT2$VPVZC?f;hTuV zi}&{PUzhak5k4&@sn0XfJ-#q$%l01+)VXgcoI3HY?r?+Fh3gAc^1g}1|J00}$>F0@ z=GH#bw_U9!z5LFX5=OoHN>-W9h^HOi% z+OE(TJN`PBUHGS% ib;zA9v)${w_AxvJByrAn~$yL;Z} z_xygp=Y8**D#~BUNY6>fFf7AaYOh4+h3Lq9>M``+_x3xx(TT2@=60ZSc4q$6&5o9? z#g4*Qo%By5Gx4);&Yzl5c6Mof*RD7FPPDujDC>mnn>XybcJ;#WmjXIjTDWwkieV$4 z)sJCV^O`ISd$L7#S7}wQX#!A!22oNx&=3oT5gNk^ieh0A)IbgQK%X445r6FYn!shr zM!aEi(XMa_^vk95B5=mMayOV)19*ujD#Qz70wM@PO~hltKu8s0HX_a|ps{|NBJemw ztFaNqdO*C&Re_f%5r{LifhGY>)3}8m+vffO!W8wtdNOMrp_7=$5B zMOY?<4f_>MQT@sTs8f#z1wiF>xl%Fit0fprg-|s|9ZDlvko%%lcYPRAl~7e`BLF(; zkjy+io3Kz4fug2F+=>zyXja95WSlh{SUle)0y(57G4ZY`&@O7wM(BMbX@;cDZk85U zPB5XdNucQj)TKzWw>||mlc>Ke<7PMkb(!S{L6LDJQ4{|cECIo*M1mqpSq_RmNQFZ_ z0#6lEC{Y4R1O?XB#Z|amf-|IQVhBK|-A16S4YDi=UJf#lGl?YaL5|^gBSZ2W^pIXJ zYXN49)x>e$f%$d?)at#}=O?T#DFAW&7nSo^MUIiIq(_thY2-LF$(u}G(k#&y7J7NJ zQS=P3O^?WEwTXedS#?zsVq|z=fvgpfW|}dPoQF1(9u^u&9#|zWQ|!;o?~dN+tk!QuG*Ul4H#*X_d?jY2s+n2+X2HTTwF;ss@W^I5&%S z9E-pj2kiK0;*;#aFNS;&ZJ`wL=;;j2oA;#ipw%_BbSwtr;H93Jc27E#zHcUcM%`<{ z__MqASk%n;Ewpj#!_->?U8LSSFoY@a6#1<eCzIwW6mGxZ&7|eZM%MQ{gmdN ze;4-F-Q*R2Ajb3e8DSi=uT$6vlOvfAx#I*7Dw$katJ627|>^zvU<;vC8 z>Iy2OqH)7DTh)Q-eP4CQ`m)sbmn^?L`|ywSwndd2>Xu(W_d&y{v>TgFx3776?7p#c z->cpi{e924wvL{4zxIyE@9J&bh?^WgwdLQ;ZMA&6qjzsc)>{{IcHJRs$LFRs%+2lO z+s?e(_}gtP>tez8$5x&k{g?gVBzo_GWwUNmw|%#sxb#BLhpVSuMV}KG=5&wj?J&z)UsMC?z~J zrmCv+gv1()&5OBKFmW3hgcuoGnOIsG8cz=6RYKu6^9HDxnOIn)7@Jt?nwcA!>Y5lQ zTk2XSr5Wm4B&Hdgnx~psSQwdZ{?2=f$%7b`N(#j#u4N@w|ArY-d!*8y;W597V+4AI{|L5=B`{E`# z+cM#Nu=dr9DshaPWoBG^@%~o#KkG(Em)-K)i;dk=*ZpOd?Kxz7Os8>uwZ5FJ`NV`f z+L`lq@B5aM_}{zY?uo+|_r))UdrVGYtlP`EHQmPLvE=qIbtzwL^{G zjt>H|`q!PGH%HcKdZvNsvURo9#%GsT&tIvnKJAg1-lwazMOL5F;>@!r-Zq?XcHi!O z{`}9&erxvL`(S4muNu^cI{5H zU8HhO`hI)nW>udp)sk^X#M9HxT>5!BvDRnF&H3UbL0(?Y2AO{*#hnhjf7D@b^@;OV z7G5=rw$*PkTcs|eDRsZQ?VDZP<0Xu8DcLFh|F$@*a{MeXy&P#ZkbE2b~P)zmU8=S@~j zHO^XlLt&9^*Gzpoz4N_TB~#ofbEcrC3uWZSEq?+&ZIKUw#-$g@^-Yxr9JKGwHc zHrLLa-G9z%0+-9`%+=j{BXyGmjkN79r15Jo%(tHm;LVpQ=U%!pBpLWRKMkf>FXx`nWp@g|Lu-lc`bIc z@GHAT-II93Q;vU}b=%VX`mc3v#?M&J%-eT6`)QYQw#fTGyxFNeUu(=B2f5C%FEgHg UH8jd|9&d+FGrA#6+2 zWyr6px%Kqx0xN7%v!e>A0t+!XR5CC+H8(miIg?fmA`3A%R5CC+H8(miIg^78P$V}q zVP!NkF)%GOGchwQG&M71En;CfWGyx@GdMXjI5jypVlcD!4ax#YH#1>nG&3P_k+4q|1e}6@C0CJD# zQNRCc?fu8b6V-|K>C`oX?aaTgerJbs zb{r1tfE})bzuAU$3x8M#$N3r#c+RO?w`08X!j59M8TI)o|1NhZ{G^2i2h?9*br26b z8;6`Th5>`A!ORTZkRyXLe>FRzm2=zUrUMHB&!)C;+Mv!4L69mEjWswC#K>%qx9dPjFF3L0O#6ru%e*%w|Y^@gD97@yG zp)chN?a2rY?PD%B>1kchB@wo1+dP7a=2Vap>_~GC*E640e`IijRXE0h9Eck?(QtvG zGvZ1ed6ccr%oqmR(gVZ!q$G{Rl)TcHdi(CdHU!I4y-vS1GJT4Rdsms0F+m}X`s+mh z>O+-;9i?nptOpm_pyJLL35t1UkgSiG3%avmnn4*1%N1Rd0-06{d$K$=^df0+7EvdL zqGijl&gDQIfBh=QY10T<3yjK?tlX1eDI$_GPcDmkXp+uI#}*PQ&8zTOkCy8IeqT4yq$9~p5fLf7ng!)}qtCJSS2 z=%Q%hVVxrCHZzjE}NrrcidAt{>ra+L6u`e`e(^XRY8q2kJs#SwIWeT$zU)peJJ=Er%B zuA8QAwn$9j1)zafgz6NyWLji?bR62gTEtr3HLmQ;sdU*?UM)JZ8pwLYvldzP0wak$ zJu8-2W5fJ1Ja%=~2UV9{wHQ?)I%8bMc-Cbk9%7!3jX7S0@>-u>Rp!s?cdL=t7*>V* zT4A!nGLjJpU(q{eAuKxaQXQ#|Zua@DZ1L{3c$Q00000NkvXXu0mjf D!pL<* diff --git a/res/life.png b/res/life.png index 6a7c981125387eefd97888f617b0eeef9574ab7e..d66ff40a2db95f0ee77f9114d6fd8d58fdaf4626 100644 GIT binary patch delta 228 zcmaFJ_n2=3D>Ju|L5Pu&m8pr9k=bMcW+fE9Epvd1sgbEgQfgYFu8EOxnyyKTX_9WD znX!qkk!7ldrG3@xERT(goL{Uj5cFX?ZC@hzY`>1eD|Y6~^#=d#nT?q}Ua%kW@pGH; dQezr3!|pr|JD%rX)PSZic)I$ztaD0e0su*iMS1`L delta 227 zcmaFN_mFP`D>JvDS%{&Dm5G6siSc9sW+fE9Epvd1L866aT5^h+uDP+9p{|LUg}JUp zvXP0dsZpwBs(F&JWs-T)=8eo*Ohl=iY|kc!V#P#Oy~%xS^&#gaXL~a+Fj#oHIEHY{ zOinn!A|s=4FVdQ&MBb@0Hggsvj6}9 diff --git a/res/life2.png b/res/life2.png index 2b01299bf5dad774f6bac01ef3ed2a9e33aef242..dccd704029166cc74b2d5e8971c286d047734f61 100644 GIT binary patch delta 257 zcmey%_m6J_D>Ju|L5Pu&m8q$fp~YkYW+fE9Epwo9ib0~WiKV5kk+DgVu8Cz@lCEW% zd5W%~X|kc2S(;f&a+1O3jm(uyE<`9)QYbEQEi17q&q&R)RWdR#GSf9M(nWT`d{({5 zbJ*&W5594}$H2hg?&;zf!Z9jUd*iPxSeh_ ztdS6Pp8U)8pxE4bO$Y8zdG7NmmPh!vf1B%NbFTaMj$dHvkm563@>sJvDS%{&Dm5HI1f#GBUW+fE9Epwo7*bv>4Zkij43Ww z0R_i73OGv2j=5|9>0i3ucz&pAQTv^lOP+o`S(cdnn01{WpU%!lz4L)4GI+ZBxvXV% zV6p(S5(?jzIY7nC)YKv+$;49EDA71o*TgI-SvSep%tALY(ZbX?*&xv%)y#79M&=48 zqEt>k$RdVf#eCL~$%oh?>(y3CT?IPph^LEVh{nX$NxgiB6a<>rpD?-i?VtZDoloJ- z2MUTiRUdMnj@2~axY54LOImxn`^=?Tc{O!uN9QRq#o2Ng|7l>h|F?IZ@21PU)|#)M z_tE}yC)&>?zy{W1tre^yHuU$ z;-qW`wHD38r)94EmV2{+^Wg_o)#f*sJvf|Vm-fEZi=4EbZF}~AUyeuLlrq}nz5knU w-4}4R^3q1;gE9A?+_x~j@=rYXb=-fpmN|2EcUj+%0(ywS)78&qol`;+0Hl1A00000 delta 299 zcmey(KaF3pGr-TCmrII^fq{Y7)59eQNP7S=2M05d%+2aPH03|M&m-w9*Sp zoo+6XnYnZI$_CZiUC*Z&tkkTE*b$>gTe~DWM4fQh8>m diff --git a/res/musicroom.png b/res/musicroom.png new file mode 100644 index 0000000000000000000000000000000000000000..c43161f549553e22e21f7d4d73b7aec534df3693 GIT binary patch literal 4333 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 Aod5s; literal 0 HcmV?d00001 diff --git a/res/sfx/bullet1.wav b/res/sfx/bullet1.wav new file mode 100644 index 0000000000000000000000000000000000000000..3678986676a1df195ff79349afba66cf3ef146f4 GIT binary patch literal 740 zcma)%+j7!S6oyl;ywo@F-ly=w&Ump}ODj55F*rbHY9R-b5Fh~(av%^64Tz<*wThjJ zV41EfAI67qZ$f6|&M%Yv`(LNM5__?|{pSJ0ynYscp3O;59x)8FMV`k`nbo_+JY>=- zDfOGwd|BEtVbOb+y!a-9h8-3Hv=cjHbd%uYFb5y-r^SMZwlfbx93n1;`cVwPM4XR|Tmk^TBHo?5a(bVf2Z(qxw4_X&82Clp z;<9HHvWXq)$He3ODsaktCb35&z|s)j-(Cb>P044I@!eH8oJ3Re>15#BRaN+yVH2;y zrm)1P>5%1YdNm&OTDGB=WFdcilujiN-^360qG{`)ksPR#sP$jHznlNKnOkk1>OZ7}|SHu^%dF*q{oTUQco66tEa@Uvbx68aCPy-wS6oxdcL Bah?DG literal 0 HcmV?d00001 diff --git a/res/sfx/bullet2.wav b/res/sfx/bullet2.wav new file mode 100644 index 0000000000000000000000000000000000000000..42d4da59f1f39833156de98c72d4d10da30268bd GIT binary patch literal 1800 zcmb`ESy$Rn5Xa+7+sF1B^u3>=5A~crq`0vNNB<~>+||L@tPpCA2pwB-#zc&5(KdZo_8Mz^sW(a2&>s+ z{`ra^NKH=yG(2BOKp5_&OA78~qjI_l4PzIc%yJHBkb4biNj1M7nXU;ktHC{F>PyI+ z2&Xz^Yj|W!E!K{1Aq_d~81OVMKw5E%bW;o+s!$SnT*X26jfBg9UF2~Y`=ntZOlf7O zDT%L_q(MIHScZ~zekKRs*3*7FmI6}r!Mg01?tTbSw`E7JL#iC<;qquP5V_EaX>~?w z-TdlMn)SpqL)-MSR9L_gER2`Qk~HaxJnM5D=oAqut3QjTD2L5Q?NWyIyS^DUcq~|l z%0$P-(L*UonqPOS0v8R?lx0t?3?)g;p(kGGE*r&3^P7HC6u8*A&qY~{yW78KkfhG) zX3}DJs=Na1?WVvOJ8q-^GWoqA(Anuo8(M4d88q26B(T~X$KI=`$%!vmv1r`}S`;h`2uZW}BV9bh@3(!P48AgaBXO~%81r&X_%@;RPM#G~O* zz~^y09hBW>F@N3H>$E#t8;{#mUIX?jwcd0{xxB&1Mf$Q-YxhT!xkCE0+r|8Da&tW# z^g8Wky;dm~3qp?1aH&K*#zw;Dp@84#_0VqD$#(~JWdCNfS`N&nug3j7gML@1)u?y2 zw>CF7RDV$RNa%yPv8~nbn=G~?hm-dDLrgTD;P_m=C{`P-PH!+8-%e+XXNCW*b8$bP z-A$*H+ne$AXgItY^n1N-=SREUYBn47TCG|U#d5h+EEWoRLAcE2a#^0wWYTGlOQn*@ zL?Uq!kH=#%mSv;SXe1H|hr+*Atxoz>{R; zkW0hQ>N>PR65xUr;T1{rdXZeb0Pzsi2wp&25ZECGvD9* z#xP)#OD-^AzAKV4DFvKNT1jRJ{~OsA;4dnp!k*><(bq5b%u$eJn5Lgx<1|b0gNeD4oAQ>|oV~ma4 z_6RVt!s>X^J?K^13jvV#mbCMB zDM3z)B6H~879An!h08<78hQF?%52o)hD_2w7;-K~5}=bN>35U1KnLMIfDiY8@zb~^ z&>;sIxZo;5@opgJDIa`62>C6|x7D!0rGn(J=u@7!;@zw-6_NpqhL{l%qNXI-D z@i0HJX7^^R%2EL*iI2kah74bph6cls%T9*QypBV+uuFxqu4od}&;M?t=V{!Hfkhv`78acL^-aga+vM@h*_f?w7XU8|L{ zsi@z5WW_mU8dJi-Umm7LN2wGT>fGl(vS_Z9XjlR8(`r0u%auYp84aE}t#HW4NamwO zeZHR>J+;Q?87dm|xQ_lLo+oim8$bQ~-0W*og=1-0@VXrb#QGq}RJ$`P@Z0lxKGNHb zno!KrR4nX2J2^fe5l$i*u{(Kp*=?3%qu)|$*-8V>qA zr!MF5q22Bdr*ajwKU!|d_LuE?NwzzhQm+c70-L@{QSn$L9Q1q7+%Bi%_}CLkv4Ygn zM~ese1?+c^z_>N_Uc0GCVuk1OY=)tMbP)ws!0+_{@5JSUe>BCa(!MsQcbn(^ez#rU z%_k#6?{zd)u8S3+RLry4^c9^-0yh>p4~Ky5^8()=p_p8$DmS~jIi9aJPrL1AwVaJd z#-QJAYpNpEsuh9fiuoLyNi$dUC1@l-Bo>XFgH9+I41{77oh|T{x}tUZL-TgJxLa@5 zs|Bc-LmgyVP0*=>P+2IIiUp8jGeiv3E>oaKB@*#?`~pN{(P%W5xMZ?%8hSu)( z^`SW$PiFJQd`bid*Zpp%t%0Nrnj#Su_!7r~ZaxRf*(_+M835=LToRLaB$LV1RXUq1 zaD2Hc)+I%4YVA(9*T2>WL&LneF^!>q-R}_taA<%9cz_AGfQ`WOC2-=3#bTiVc7Pm! z{Ri3qkog)}HeV=mC0;03s$#8Hmn2zH8miiCYMRz+wE-Q1c8j=zts;XlajuEr4eo$4 x03ui^ctGiEK&)@V*Q^omTAfD{+4`J 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 literal 0 HcmV?d00001 diff --git a/res/sfx/explosion2.wav b/res/sfx/explosion2.wav new file mode 100644 index 0000000000000000000000000000000000000000..60659bf8dffbe30b34b46ebafcc6205db444e1cc GIT binary patch literal 10020 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 literal 0 HcmV?d00001 diff --git a/res/sfx/menuchoose.wav b/res/sfx/menuchoose.wav new file mode 100644 index 0000000000000000000000000000000000000000..e8a5defedbc8752c45fc352cc8847969355dc6fb GIT binary patch literal 5741 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 R&;RRRP5hsM|EI5n{|i8F9M1p% literal 0 HcmV?d00001 diff --git a/res/sfx/menuselect.wav b/res/sfx/menuselect.wav new file mode 100644 index 0000000000000000000000000000000000000000..6d2e5f1cc1bfb8a7681efa32d94fe2130bd1d42e GIT binary patch literal 1471 zcmWIYbaUIz%D@or80MOmTcRMqz`(!=go4^Y2AIvkkdj!EI2ovBrAb+R{aLTfy80Ds zjVkKvchAzRs;}R)$hf?|zW#hfRBe5I?-8HmIw0?CqggeO>OGR?T?3@+Pj{M@g6R4) z?Uu!0#@x-#ra}W529bG!Jkv*YksAU4U?9)c literal 0 HcmV?d00001 diff --git a/res/sfx/playerhit.wav b/res/sfx/playerhit.wav new file mode 100644 index 0000000000000000000000000000000000000000..def025b84a7fe94659ab2cb315283c76cf78d5b6 GIT binary patch literal 9500 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 literal 0 HcmV?d00001 diff --git a/res/sfx/playershot.wav b/res/sfx/playershot.wav new file mode 100644 index 0000000000000000000000000000000000000000..e518440c33041c9d8e3a0675ba60331fdeb4c4bf GIT binary patch literal 2491 zcma*oZBNrc902h7PT%P_@V##&zA#aP7$b?s7Xkxfe1JJ+W8i=?R@ObV>*&^Qj4|1x z-B>X^MIMBqWS9(Nj0ERL@XL7DYrDH{OZbtrS9aI^@BaV0^*`?IJ-TAC3_lopH1^(e z{i?-cxdi^M-2e@>ecAHX=CP$fABv*9vYbvU%CA3-#=rAaYB?pc_j<>jHb*pB-JP0U zS^0e$4*LVUyT>QhYEjDMcF%VzA10*o>VBSKsJU=B#88w!AuRAtn&U#0%{J+wsHwMe zG_4dfnMA%?-QSeu(z;wMQZBbU6d4(L@zCLLRMPQybmJFLub&i4rP9w0MJZJ4^}U^1 zZBJT|eBUvfWJ+RMx&HWCf(0)k9vA~ zZexSGZgqFx?|F)wnVIqCkSr8;I-LV|k$cd%*Bc}PJUBQw#2nRXwbLJ`O?)6Qcn48m zGt6UBGjp!)hT1EZsF+$0c|$3)C#zaFc;HSw(DG3g2L+~rQRmHv?b*uf(2 zndoTJ&n_*26S_q@p-M2zg-47#AZh;gZROzy(eG47vBghD+zbYjq5q&<^J~W&)lr zQ{X4VFbz#%eDKXYw!x(s1Y^)}D%h=)a0H4;9l^y>5i?Lnk(U@+iDg0p36Rwp~2dg41q#1g!FytXAHjic~nMvh0xlw&jy6esYZQ+*=b$k~&fFV&GugyCg21pR&?I}pCu7PNY#0Gu IprOC=Kl&c-+yDRo literal 0 HcmV?d00001 diff --git a/res/sfx/startgame.wav b/res/sfx/startgame.wav new file mode 100644 index 0000000000000000000000000000000000000000..d5312715a4ba21966dd9fd4c57caf172e337e71e GIT binary patch literal 8068 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 delta 104 zcmaFOGlzRZ2@}iLjb;5z+=dn*Mut{~23DpflLeTSQ24gY0h9Y#)HZKsPG+3^lr>WK x+NW}U1_lN}PZ!4!j+x0R$qfu@8op}G3@kgZ diff --git a/res/skytop.png b/res/skytop.png index a25d59e4897ccd921ca256e774844d81a4225e94..f67fc25b0f2c0c3482105880fc4c4671d34017b9 100644 GIT binary patch delta 913 zcmZqGZr0dP#AV3CA)*|v6p-ztByVGFRPQJu;-6TOr>R=tW|JLbTaptXV5XXBloB2q zQ&m-ZLSl`@=EYnqn7E7#LJTdfOf0NSCWrDWBXgU0165KCQVa}}(h_yelMIY?O^l3^ zb(0KCQ*~3#43kZg4U7_vQc^a5=gngBAV#HqPU%Bkpm zJr+FcxWooAW;S{D&R0t~jauSsgt)(*%MLU=-)iI?@cOu%!S*@*PS@TYToW#>P>~xJ zF(s+SG`mE`mhE0y_tfq8QhvrH-B$duep+R(lYZf*xt7dr8g7y0kq?dK((NToZB_>r z)Nc&BzH90AxRs8xf3OAYTfSr;bMLK>sul}b7yL^*v)b_6k5|=A@BcWNFOYq|?{dVn zgpxL1tt*dXm#zJ$b|uas|K`MdihDEj7N-^V>|Fo%t4r*;#*b@e=6@A5KKfjC$*fM< z!&O&rrMHD9gk`PE*jvf@J(5Xp`*p2za?kD3Z43S9*6X?HI(?D2wqqGrjOE#B>vl$6 z`{fgxFSWE|xAgj73!%_V(Msr#(gYJnla4TfVjb<$s4k8KTwYsgXnO1!5&6@vLZ%;p> zaM|RC-tw<3@5KJFm}*7F{z=I^6ZC%9$K+yJl_MuUmhXA|z5e#Sra9gAFU5YaAKZWX ULPhhEPYgid>FVdQ&MBb@0NUc1@Bjb+ delta 1302 zcmV+x1?l>OD1|GqatbjS5GY2kMWNP(MLu|TWd4IODW1aU+*3>8jd|9&d+FGrA#6+2 zWyr6px%Kqx0xN7%v!e>A0tzuWR5CF-FgZFhlT{5P3NbiTGBG+ZIXW_vf(=n5Ghr|@ zW;QuDEjKVXIW06ZHe@Y0VK`$gWo9yBI59OdGc`Ccv-b^Z0!TAqFf(R0IX5jgFgH0Z zG&442EjVE~V=ZN7GGaI}H8V3cI4~k0b96;^bUJrvWo|tpGB7eWEif`IF*sB*F*-0g zIx;pbFg24M4?B|;5EhgA5LtgO@7t>Y00b6EL_t(oh3!_+vg;@eGo~*6|Gy=knjOg| zNzd%PFQ-pC+e||M1F|eTX;1mxPk*^y{@*~pxPjmPd+=vptH0;aZ~rkJ=*@5WOMCyd zFL0o@{eSX*AHT=o!rFd~KyRM8XkkE$8C>Y0-6PkEZ|T5)@PFswZ*716+Siom=XDoH z_O|!QQrhl&`w{}L>of0?@q7ID=oN`Z1a2Z4*`Aj6=k{i+^u$~wG~12G(s$(;YziA~ zh5hev;2->MHa7Fg$ECbWkDHP9{5Rjw{v5mWbK^w4d_=2JzN5uy9D^;!uNd%G{Z<^@ z&)Mhy3CA6e?|Q(q!LfgcN9iDngNjk&$iG;r!<2T664#bZ;--~^rdPJFjCy*et=%vu zZd_a)8~g6({Ke+REIE@=3J(q7@sPqIc(U@0qZM(i?a@WN>yjlaPu0-nUAit+-MIn; zt1zgmE{x&c5rw`-98pyb>E5xUtny3g0c;g+6vxbt71!Xb z!hFRxP}kTat3>)SVQZ$Nn z9XYiFMA0R}1Yv(fjO;KUhv^ZCA9T`7=sXN2j!@`f9vu%RVg@*+$~->?D;ANR%vA>S zKFw{wqCMezmPHelDI#NH8|5SCxDwRRAKyK6mFgBN9FtiP=*c86*U1x46<=(Te1q5$IQS$9-@qeWSKCxs=!l5MbkN!Au@^JGMgep479RKhBujK5H)XUYhe>at^Zi&75w@0Fd+M}|>_C5p?QaRDPQn3}QST1EWeOi0EFsZU z%C4k+{~8=wKPm$=Ez%{C))>J78J<-Kyw;|fvwI?QmtYHuv={UU>%k&X?{aPdJRtCV z*K?zc24ByiGJ&9E?pa7S2;?zH2?qr M07*qoM6N<$f=tk9YXATM diff --git a/res/stars.png b/res/stars.png new file mode 100644 index 0000000000000000000000000000000000000000..649562a4dda997e1f60dab3d75029c03f56bcee9 GIT binary patch literal 4606 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 C<)A14 literal 0 HcmV?d00001 diff --git a/res/start/bg1.png b/res/start/bg1.png new file mode 100644 index 0000000000000000000000000000000000000000..55ef3b53e12c8fb50d8b6fb75865c394c2395e33 GIT binary patch literal 6944 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 T?|$d`4+Cv=1GQom%jf?GS$%_^ literal 0 HcmV?d00001 diff --git a/res/start/bg2.png b/res/start/bg2.png new file mode 100644 index 0000000000000000000000000000000000000000..d03e6cadcc64ca8202b0419d9a175af0af8b2150 GIT binary patch literal 4522 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 literal 0 HcmV?d00001 diff --git a/res/start/bg5.png b/res/start/bg5.png new file mode 100644 index 0000000000000000000000000000000000000000..934b952762aa0b7233f4daf5717f3e8ea4243618 GIT binary patch literal 4714 zcmb_g2UJtp)@CdW%7~~GL4*)Q0SURJKnR2uN&rEmD++`Z2qA?ekkCY>DN>}^a1arZ zCL)7K6&V;6b*L&9nt=2UB2C~Ww�m-uq|0^=GY{bM86&eEZvbf9IUF?ipJf^DUcY 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 literal 0 HcmV?d00001 diff --git a/res/start/logo.png b/res/start/logo.png index 05a1ae2d9858480c7bef3611adb93700c1c18fb6..3ff08e2866905fd9e25ee4916c15edf321311a61 100644 GIT binary patch delta 1373 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 g7sS>Vw4SZYre41W0+qQe*Wm}Crlh4Px# diff --git a/res/start/splash1.png b/res/start/splash1.png index 57d37655971f3379425dc8456429de34464ff1d7..856532a9633f9cb42adc91134573b2a2da6f5181 100644 GIT binary patch delta 4352 zcma)gRa6rW!2MvrP#PpeX+;o$jUF``Mk9?NF^~>vsnOjH(nxntkQ7nUkwdyfx|;#t z@BjM$UcPf~oO928xX%~ES0>96rKzq+@|gZH001C?E6Kh6&jbG-34#CV*-FxN14}*; z7X-`$=0Mcy#;f%yb^!NR;?K5ht~C`3dQ!r!z*G>Jnl#Qk5y z2ho8Div9;dk(5yqhW{PjkZ8UT77{kM60+dq780?t;ua7P6y~-N;pgKP1VcVREiKJ0 z5D1|rCsI3HrTw@a^5P7NI) zQ)5P`$$c@wh#>-Eq=?&Py96JA{)dm*f0(ns4l<_rC)Lcxc`E)JqrdspEPv!75@+q} zNR!*%V=c22x85{9^3KbyoMn!c-nfSkk1XyaqCE##>z!lLAM)PQOyER zI_~6hz0pF)6OVneXgU)s&{fez%FRzxxw0bNFWNC9NxV#ri|4}Tn!4! zVZ`N9g{cIOKlKPFbBfbHl#0^GL4gQ3kfr(JX>H(n0Pd^1PdQE`MO6CpN6&!Wb@8dM zNK0vSzl@45Ly+Y85|{AOpFw&}UYIY*_;=U>U>E+K>3miEYz^aE#;|KW3Z2i%q#yle zdA!DDbMn4(*6K}DcyP=6jQviQa(LY>3UbW6fOJaS&+piys-bVf`lI?1B*q`{w zpy4`JswcR=DX}&V->s*)w!xWe3)IUa2vF>-bcvc-aED+&(&%L=={O zWCBnrK(io?&f_1C2FWr1%IuTdoYX zhx43TSqOKx{9uYPCJDBxcnOPBP+xd@h4yRj0xc%)b$YDS7okf=+K4EfG!z}HN(_2$%pTfxR;vx5(tz@q-|eQx`^|N<2PCD&57soE=nco6;x0JJmapxedE9v+ za<)8Mo!Fe;Z_x3U;YR=s`r7OqUKY4%ku98@RG&KjwnD>tlMaGHo_N`#+pTB*CgVHp z7H|^AgXK9Wp{S^|kfEm1{>)z>d8D3NcUc`DUMoM`Ajh%036{2oP|s=X(f)QB@uKAw zB2F;X+96_n{>c6;=>Box4#)Yw(eT5hN|vP1)|#d_2w-@&)Pd$UpT(-Lk0~IE9$ja& zJ>4Hs;Cf7nKn(ON)z%?8LHzCDcIR={h@@>Y%K$NP1MU$*!#s-Px(Y~JMZRwcT# zVW;}F@6*pc`YvPde;QPxwr~wJB_2#%T9(!w9jQ~q&L_A5_;?EjQz#42U%OGSj#fj? zHyuvaJr=q81*ZdqTNl;DS4j4>KijSc8&6Q5m6@{Dr z%-nU9{b)C&geibD4Y|Q&+NQ&Ygc8pgZ#_g0r12q$EZa80$tH(l^?-P)&)RoJj5)qZhLaiH_K1OgKhJYFnh96q4w`XvlK_VM_<{xk z_kn>5uQLw;l#n62G75!9%ARhy#H4OBJF&~uL%LLQ`HCPD34UE&ZP+Zh*)T!}*@`!8 z%C67AAI#&+Mk;~tHs4K;?Z4FAtbL~SXr=w5Yb7q%-G+h2sr{~vo;lP9^v_-O>1L!G z)|s*(Leb*XQjR>+Ni4{YrO~p*no!A}!^Wu`e!BhbABez*e>YrOWQzk}dYS+yu;EB2 z{CYVMh`9QN+!{6y%X@0}{C+_vryLQ4Qw!MLOmQQjbb1+JlSkueMp7Pmc4cs*>seL|oh1 zm@RX~PsjR(VQQI+Dd$(Yy#aAb_1E5dnzor?4QFj(%M0CJ1tH8&D*nJ(1Gw3)C7Fgr z)q%z6L2onP!T}GEyul<$7gr7^n-n8Gbxj{a_|hG|CJ&86AW#A%0VsSb>+y5N)uq?EhkJl(1I zZGu|wWW@dm?W;K56Rnlqr-XQ|X$>b|dP()qO9yXEDSq;r9*e}0vkngvRi=M$T#pqL z$UJZlgnAab@xQEDmM~(&;FANLbXosWhI$*;!_=En#c8|E!W)eX9+AfVgCd`siyHY` zD5QHt6SWfv^(-10=hTP3jsn`0O;cp|ayGj^BCI5v{rpN23|M#9hP6;u=$dn@S7=;{ z!KnuE=xDuolbpJP4QUBD=2pKmR5#m(>GnEc?Dh_VCbYwzxuU6E<|WpsI72UmgXGp| zSS*%$dIV3vexxm+x=GD{HpucKrEGhKw=`WB6Ir>1<&G6rL6G_?58KoQbxI}8%g{52 z)za9gc;e=rZXXM`Jp-28U>(OYs_{Cdy9vtgf^l=q+m_`Ix+p&i9WjY6_pXM!r#O^! z<{37r?A|(XY z%QrGomb#b=ow(5_G2$Wl+bLHo#$=!p&=&Qgz$#P}*3D(7XdVt{X9w8izXXE}>iYSs z&s7`5tGdgMStj{r0S?$h(4^+meXR@Z14IirN{1N7gK;|8#>_i|HnD1uy|Lj1 z;r`rJfK-)VMd5tL%|VA2*IQ@lg1jR?<>|${eRI={I<1-fW>hKq!hdFQaINi-$d$YA znypX4q)jas2~o~Jo{qaUIHZzXh=?-m)`Q;cMHGAM%0@?v*uu`W0;wg19t6@}19X3! z*B*XQj!t0oy-gO)%oJD%_CSwbe+T{*=%IV9be?PxyYi9&^!rZ8ICwcW}d!BfNt5EncG2#j|wc8mIbx zz15)5C+?ONN^iNRmV2J61oNCaO<_`SLn<-guk7JG+a2fuMO7p_QMdbb$?u(pPK@#=c3=D~eP$bG721Q;c zUWdBry6Hn~Dc&n)&5;$jq9IJk%pKV`fljyvVuh+2G77Z;@`5#b0IJgD9L%JQEGP!;Kyi~P_bsm^J&CgrMQo6mVRfBR5P*!6t(4m zCKyBEPkHuRiJJmQi<7%O91rzD$B4zFxpk2og{^|EbsleF@NW1O`ry7c zgbQ>k^`~#$D0BavzPM?xm5f6T`@OY`6}vOdoa@-#?kO=|jKtqMA0BC~3`aQii>p#; z)KK))c=h7HCb7iFg{?dSvq#$D(q^1 zXv~!oa_vx3l#i0a%c#rB#+%s^P> z&89H;GQv`^(f*)4xfJk7EtmqULHJADn`Ddb&|)Ly?t$WNJ9o*h-&Q5>DSrxV=K*c* z*uZdLd1micR}GM)G7ss(HYPM@9f=I*)^LyQmmC$!Gmg;GdaP7YxA~?1ja(cr?$9GQ zYmJWJ?RkYkgL^_iQ_D-D~xMbDNxN;mCAio)&CfbShfTjz0=4(Ii z;?OCiOp|E;B=NT$S=Oo{I57>X!2I>bl?lV_mj1$3im}-JL8k&}vao44R-9sz40m*} zPfzfjY2R~0GRE37NhxtIOLzG-w{EgpuBTKqO#Pt5UyFZ1DsMQ>CtL3~D3cvYeBgLf z-OMYU8oB{68j^{ABNPjjHxNu&E?%P&_1K-MzHeR=D8PiI_1#x+we+XDjxsZ1ta=Bl zk+X3TQ%}wF@VeN?oPW_c39E(|!z+CAB3sqNVmRIbo^LZKS}17S448T~s`97P!z~|! z9CiiML7@<$z>^{a;`S^kH{_0u($sr$pi1HV1Zaapy)l8~%kwMeg6@@ipeD|V!C&8W zRmY608C#|U8xaPRMp(^eN7bfq_l}fc(gbZ_k-%HCM`NA3H{fcS5J0p$z9RmF121HQ z_SCooPnds-EDXPkmu1ozz2BiU>*_*IUpS}d6wj*P3Tz~d{e+Y3mN4u&7NLgq{O(%q z%r=h}DvcJu?lke;v`cB!Yf+xOcuoS-WMl6%0T-N_l&-dOy7Gj!8T)?LALu%LxpHG- zyCbbN=hfny)G0!s1z*F`m8vHSWSOE|c6(`5YsiA^|dGO*i;KhcCO(#DPY3DIE&@Qe_gsN>%V})6R{8wn3^n1L&>wk2S$8B9bisPn0T@ldJ3&@5t`EK3;bY_x zIY#x;Te8?rV)!tnYR95#y7^Lo8pVmuM~gB?(@br-T+5yI!`gU!S|~bH`>qq789tfY z_IUZ~eS|RoqN>T{k8e-q)fpqdka6*;?`H+AJ-sH`XB}D7kwY_E5>gX7J=2rN{-Ss8 z<$YEP69y&URWJT5nDyft|qRiboJF9JuG^8e-BEPa^ave zrF_>OiN*q$)oY2nM%?;GPfz1}e(1jL7^v4t)nN`q%+j2C9S{jxxgM$$x$Z7tX@g Kyw=S1_P+s`BMa~V diff --git a/run.sh b/run.sh index f544178..a96a093 100755 --- a/run.sh +++ b/run.sh @@ -1 +1,2 @@ -/Applications/Genesis\ Plus.app/Contents/MacOS/Genesis\ Plus out/rom.bin \ No newline at end of file +#/Applications/Genesis\ Plus.app/Contents/MacOS/Genesis\ Plus out/rom.bin +/Applications/ares.app/Contents/MacOS/ares out/rom.bin --system "Mega Drive" \ No newline at end of file diff --git a/src/background.h b/src/background.h index ab3a23a..d5b4db5 100644 --- a/src/background.h +++ b/src/background.h @@ -1,11 +1,13 @@ #define BG_I 8 -// zone-unique block: 64x64px ground block in sky area, only visible in zone 0 -// world X=256 = tile col 32, placed in sky row block 1 (tile row 8) -#define ZONE_BLOCK_WORLD_X 256 -#define ZONE_BLOCK_COL ((ZONE_BLOCK_WORLD_X / 8) % 128) +// doors: one per zone, placed in sky area at tile row 16 +// base X per zone chosen so tile cols never overlap between zone pairs: +// zone 0 → cols 1-31, zone 2 → cols 33-63 +// zone 1 → cols 65-95, zone 3 → cols 97-127 #define ZONE_BLOCK_ROW 16 -bool zoneBlockVisible; +#define DOOR_COUNT SECTION_COUNT +fix32 doorWorldX[DOOR_COUNT]; +bool doorVisible[DOOR_COUNT]; fix32 prevCamera; #define PARALLAX_COUNT 8 fix32 parallaxAccum[PARALLAX_COUNT]; @@ -15,6 +17,7 @@ static const fix32 parallaxMul[PARALLAX_COUNT] = { s16 bgScroll[28]; u8 bgOff; +u16 bgPal[16]; void loadBackground(){ VDP_setScrollingMode(HSCROLL_TILE, VSCROLL_PLANE); @@ -23,6 +26,7 @@ void loadBackground(){ VDP_loadTileSet(sky.tileset, BG_I + 64, DMA); VDP_loadTileSet(ground.tileset, BG_I + 128, DMA); VDP_loadTileSet(skyRed.tileset, BG_I + 192, DMA); + VDP_loadTileSet(door.tileset, BG_I + 256, DMA); // for(u8 y = 0; y < 14; y++){ // for(u8 x = 0; x < 64; x++){ @@ -41,29 +45,35 @@ void loadBackground(){ // } // } - VDP_fillTileMapRect(BG_B, TILE_ATTR_FULL(PAL1, 0, 0, 0, BG_I + 192), 0, 0, 128, 8); + VDP_fillTileMapRect(BG_B, TILE_ATTR_FULL(PAL2, 0, 0, 0, BG_I + 192), 0, 0, 128, 8); for(u8 y = 0; y < 3; y++){ for(u8 x = 0; x < 16; x++){ - VDP_fillTileMapRectInc(BG_B, TILE_ATTR_FULL(PAL1, 0, 0, 0, BG_I + 64 * y), x * 8, y * 8 + 8, 8, 8); + VDP_fillTileMapRectInc(BG_B, TILE_ATTR_FULL(PAL2, 0, 0, 0, BG_I + 64 * y), x * 8, y * 8 + 8, 8, 8); } } - // place 64x64 ground block in sky area (zone 0 only) - // VDP_fillTileMapRectInc(BG_B, TILE_ATTR_FULL(PAL1, 0, 0, 0, BG_I + 64), ZONE_BLOCK_COL, ZONE_BLOCK_ROW, 8, 8); - zoneBlockVisible = TRUE; + // place one door per zone at a random position within each zone's unique col range + for(u8 d = 0; d < DOOR_COUNT; d++){ + doorWorldX[d] = FIX32(d * 512 + 8 + (random() % 31) * 8); + u8 col = (u8)(F32_toInt(doorWorldX[d]) / 8 % 128); + VDP_fillTileMapRectInc(BG_B, TILE_ATTR_FULL(PAL2, 0, 0, 0, BG_I + 256), col, ZONE_BLOCK_ROW, 8, 8); + doorVisible[d] = TRUE; + } prevCamera = player.camera; for(u8 i = 0; i < PARALLAX_COUNT; i++) - parallaxAccum[i] = fix32Mul(player.camera + FIX32(256), parallaxMul[i]); + parallaxAccum[i] = F32_mul(player.camera + FIX32(256), parallaxMul[i]); // write initial scroll values so first frame has correct parallax - s16 initScroll = fix32ToInt(-player.camera); + s16 initScroll = F32_toInt(-player.camera); + for(u8 i = 0; i < 20; i++) + bgScroll[i] = initScroll; for(u8 i = 0; i < 8; i++) - bgScroll[27 - i] = (initScroll - fix32ToInt(parallaxAccum[i])); + bgScroll[27 - i] = (initScroll - F32_toInt(parallaxAccum[i])); VDP_setHorizontalScrollTile(BG_B, 0, bgScroll, 28, DMA); } void updateBackground(){ - s16 scrollVal = fix32ToInt(-player.camera); + s16 scrollVal = F32_toInt(-player.camera); // accumulate parallax from camera delta (not absolute position) // this avoids discontinuities at world wrap boundaries @@ -75,7 +85,7 @@ void updateBackground(){ // update accumulators once, reuse for top and bottom for(u8 i = 0; i < PARALLAX_COUNT; i++){ - parallaxAccum[i] += fix32Mul(delta, parallaxMul[i]); + parallaxAccum[i] += F32_mul(delta, parallaxMul[i]); if(parallaxAccum[i] > FIX32(1024)) parallaxAccum[i] -= FIX32(1024); else if(parallaxAccum[i] < FIX32(-1024)) parallaxAccum[i] += FIX32(1024); } @@ -83,18 +93,51 @@ void updateBackground(){ for(u8 i = 0; i < 20; i++) bgScroll[i] = scrollVal; for(u8 i = 0; i < 8; i++) - bgScroll[27 - i] = (scrollVal - fix32ToInt(parallaxAccum[i])); + bgScroll[27 - i] = (scrollVal - F32_toInt(parallaxAccum[i])); VDP_setHorizontalScrollTile(BG_B, 0, bgScroll, 28, DMA); - // show ground block only when zone 0 copy of these columns is on screen - fix32 dx = getWrappedDelta(FIX32(ZONE_BLOCK_WORLD_X + 32), player.camera + FIX32(160)); - bool shouldShow = (dx > FIX32(-212) && dx < FIX32(212)); - // if(shouldShow && !zoneBlockVisible){ - // VDP_fillTileMapRectInc(BG_B, TILE_ATTR_FULL(PAL1, 0, 0, 0, BG_I + 64), ZONE_BLOCK_COL, ZONE_BLOCK_ROW, 8, 8); - // zoneBlockVisible = TRUE; - // } else if(!shouldShow && zoneBlockVisible){ - // VDP_fillTileMapRectInc(BG_B, TILE_ATTR_FULL(PAL1, 0, 0, 0, BG_I), ZONE_BLOCK_COL, ZONE_BLOCK_ROW, 8, 8); - // zoneBlockVisible = FALSE; - // } + // show/hide each door based on proximity to camera center + for(u8 d = 0; d < DOOR_COUNT; d++){ + fix32 dx = getWrappedDelta(doorWorldX[d] + FIX32(32), player.camera + FIX32(160)); + bool shouldShow = (dx > FIX32(-212) && dx < FIX32(212)); + u8 col = (u8)(F32_toInt(doorWorldX[d]) / 8 % 128); + if(shouldShow && !doorVisible[d]){ + VDP_fillTileMapRectInc(BG_B, TILE_ATTR_FULL(PAL2, 0, 0, 0, BG_I + 256), col, ZONE_BLOCK_ROW, 8, 8); + doorVisible[d] = TRUE; + } else if(!shouldShow && doorVisible[d]){ + VDP_fillTileMapRectInc(BG_B, TILE_ATTR_FULL(PAL2, 0, 0, 0, BG_I + 64), col, ZONE_BLOCK_ROW, 8, 8); + doorVisible[d] = FALSE; + } + } +} + +#define BG_THEME_RED 0 +#define BG_THEME_GREEN 1 +#define BG_THEME_BLUE 2 + +void loadBgPalette(u8 theme) { + u16 coloredPalette[16]; + u8 i; + for(i = 0; i < 16; i++) { + u16 color = shadow.palette->data[i]; + u16 r = color & 0xF; + u16 g = (color >> 4) & 0xF; + u16 b = (color >> 8) & 0xF; + switch(theme) { + case BG_THEME_GREEN: + coloredPalette[i] = (b << 8) | (r << 4) | g; + break; + case BG_THEME_BLUE: { + u16 newB = r > b ? r : b; + coloredPalette[i] = (newB << 8) | (g << 4) | (r >> 1); + break; + } + default: // BG_THEME_RED + coloredPalette[i] = color; + break; + } + } + memcpy(bgPal, coloredPalette, 16 * sizeof(u16)); + PAL_setPalette(PAL2, coloredPalette, DMA_QUEUE); } diff --git a/src/boot/sega.s b/src/boot/sega.s index 6dcde0b..097c35a 100644 --- a/src/boot/sega.s +++ b/src/boot/sega.s @@ -92,126 +92,6 @@ SkipInit: * *------------------------------------------------ -registersDump: - move.l %d0,registerState+0 - move.l %d1,registerState+4 - move.l %d2,registerState+8 - move.l %d3,registerState+12 - move.l %d4,registerState+16 - move.l %d5,registerState+20 - move.l %d6,registerState+24 - move.l %d7,registerState+28 - move.l %a0,registerState+32 - move.l %a1,registerState+36 - move.l %a2,registerState+40 - move.l %a3,registerState+44 - move.l %a4,registerState+48 - move.l %a5,registerState+52 - move.l %a6,registerState+56 - move.l %a7,registerState+60 - rts - -busAddressErrorDump: - move.w 4(%sp),ext1State - move.l 6(%sp),addrState - move.w 10(%sp),ext2State - move.w 12(%sp),srState - move.l 14(%sp),pcState - jmp registersDump - -exception4WDump: - move.w 4(%sp),srState - move.l 6(%sp),pcState - move.w 10(%sp),ext1State - jmp registersDump - -exceptionDump: - move.w 4(%sp),srState - move.l 6(%sp),pcState - jmp registersDump - - -_Bus_Error: - jsr busAddressErrorDump - movem.l %d0-%d1/%a0-%a1,-(%sp) - move.l busErrorCB, %a0 - jsr (%a0) - movem.l (%sp)+,%d0-%d1/%a0-%a1 - rte - -_Address_Error: - jsr busAddressErrorDump - movem.l %d0-%d1/%a0-%a1,-(%sp) - move.l addressErrorCB, %a0 - jsr (%a0) - movem.l (%sp)+,%d0-%d1/%a0-%a1 - rte - -_Illegal_Instruction: - jsr exception4WDump - movem.l %d0-%d1/%a0-%a1,-(%sp) - move.l illegalInstCB, %a0 - jsr (%a0) - movem.l (%sp)+,%d0-%d1/%a0-%a1 - rte - -_Zero_Divide: - jsr exceptionDump - movem.l %d0-%d1/%a0-%a1,-(%sp) - move.l zeroDivideCB, %a0 - jsr (%a0) - movem.l (%sp)+,%d0-%d1/%a0-%a1 - rte - -_Chk_Instruction: - jsr exception4WDump - movem.l %d0-%d1/%a0-%a1,-(%sp) - move.l chkInstCB, %a0 - jsr (%a0) - movem.l (%sp)+,%d0-%d1/%a0-%a1 - rte - -_Trapv_Instruction: - jsr exception4WDump - movem.l %d0-%d1/%a0-%a1,-(%sp) - move.l trapvInstCB, %a0 - jsr (%a0) - movem.l (%sp)+,%d0-%d1/%a0-%a1 - rte - -_Privilege_Violation: - jsr exceptionDump - movem.l %d0-%d1/%a0-%a1,-(%sp) - move.l privilegeViolationCB, %a0 - jsr (%a0) - movem.l (%sp)+,%d0-%d1/%a0-%a1 - rte - -_Trace: - jsr exceptionDump - movem.l %d0-%d1/%a0-%a1,-(%sp) - move.l traceCB, %a0 - jsr (%a0) - movem.l (%sp)+,%d0-%d1/%a0-%a1 - rte - -_Line_1010_Emulation: -_Line_1111_Emulation: - jsr exceptionDump - movem.l %d0-%d1/%a0-%a1,-(%sp) - move.l line1x1xCB, %a0 - jsr (%a0) - movem.l (%sp)+,%d0-%d1/%a0-%a1 - rte - -_Error_Exception: - jsr exceptionDump - movem.l %d0-%d1/%a0-%a1,-(%sp) - move.l errorExceptionCB, %a0 - jsr (%a0) - movem.l (%sp)+,%d0-%d1/%a0-%a1 - rte - _INT: movem.l %d0-%d1/%a0-%a1,-(%sp) move.l intCB, %a0 diff --git a/src/bullets.h b/src/bullets.h index 7a813c9..1e1a831 100644 --- a/src/bullets.h +++ b/src/bullets.h @@ -1,68 +1,39 @@ #define BULLET_OFF 8 #define P_BULLET_OFF 16 -static void doBulletRotation(u8 i){ - if(bullets[i].anim >= FIRST_ROTATING_BULLET && !bullets[i].player){ +void doBulletRotation(u8 i){ + if(bullets[i].anim >= FIRST_ROTATING_BULLET){ bullets[i].vFlip = FALSE; bullets[i].hFlip = FALSE; - if(bullets[i].angle < 0) bullets[i].angle += 1024; - else if(bullets[i].angle >= 1024) bullets[i].angle -= 1024; + fix16 a = F16_normalizeAngle(bullets[i].angle); + s16 deg = F16_toInt(a); + u8 quadrant = deg / 90; + u8 inQuad = deg % 90; + u8 frame = (inQuad * 9) / 90; + switch(quadrant){ + case 0: break; + case 1: frame = 8 - frame; bullets[i].hFlip = TRUE; break; + case 2: bullets[i].hFlip = TRUE; bullets[i].vFlip = TRUE; break; + case 3: frame = 8 - frame; bullets[i].vFlip = TRUE; break; + } - // 0 - 256 - if(bullets[i].angle >= 1008 || bullets[i].angle < 16) bullets[i].frame = 0; - else if(bullets[i].angle >= 16 && bullets[i].angle < 48) bullets[i].frame = 1; - else if(bullets[i].angle >= 48 && bullets[i].angle < 80) bullets[i].frame = 2; - else if(bullets[i].angle >= 80 && bullets[i].angle < 112) bullets[i].frame = 3; - else if(bullets[i].angle >= 112 && bullets[i].angle < 144) bullets[i].frame = 4; - else if(bullets[i].angle >= 112 && bullets[i].angle < 176) bullets[i].frame = 5; - else if(bullets[i].angle >= 176 && bullets[i].angle < 208) bullets[i].frame = 6; - else if(bullets[i].angle >= 208 && bullets[i].angle < 240) bullets[i].frame = 7; - else if(bullets[i].angle >= 240 && bullets[i].angle < 272) bullets[i].frame = 8; - - // 256 - 512 - else if(bullets[i].angle >= 272 && bullets[i].angle < 304) { bullets[i].frame = 7; bullets[i].hFlip = TRUE; } - else if(bullets[i].angle >= 304 && bullets[i].angle < 336) { bullets[i].frame = 6; bullets[i].hFlip = TRUE; } - else if(bullets[i].angle >= 336 && bullets[i].angle < 368) { bullets[i].frame = 5; bullets[i].hFlip = TRUE; } - else if(bullets[i].angle >= 368 && bullets[i].angle < 400) { bullets[i].frame = 4; bullets[i].hFlip = TRUE; } - else if(bullets[i].angle >= 400 && bullets[i].angle < 432) { bullets[i].frame = 3; bullets[i].hFlip = TRUE; } - else if(bullets[i].angle >= 432 && bullets[i].angle < 464) { bullets[i].frame = 2; bullets[i].hFlip = TRUE; } - else if(bullets[i].angle >= 464 && bullets[i].angle < 496) { bullets[i].frame = 1; bullets[i].hFlip = TRUE; } - else if(bullets[i].angle >= 496 && bullets[i].angle < 528) { bullets[i].frame = 0; bullets[i].hFlip = TRUE; } - - // 512 - 768 - else if(bullets[i].angle >= 528 && bullets[i].angle < 560) { bullets[i].frame = 1; bullets[i].hFlip = TRUE; bullets[i].vFlip = TRUE; } - else if(bullets[i].angle >= 560 && bullets[i].angle < 592) { bullets[i].frame = 2; bullets[i].hFlip = TRUE; bullets[i].vFlip = TRUE; } - else if(bullets[i].angle >= 592 && bullets[i].angle < 624) { bullets[i].frame = 3; bullets[i].hFlip = TRUE; bullets[i].vFlip = TRUE; } - else if(bullets[i].angle >= 624 && bullets[i].angle < 656) { bullets[i].frame = 4; bullets[i].hFlip = TRUE; bullets[i].vFlip = TRUE; } - else if(bullets[i].angle >= 656 && bullets[i].angle < 688) { bullets[i].frame = 5; bullets[i].hFlip = TRUE; bullets[i].vFlip = TRUE; } - else if(bullets[i].angle >= 688 && bullets[i].angle < 720) { bullets[i].frame = 6; bullets[i].hFlip = TRUE; bullets[i].vFlip = TRUE; } - else if(bullets[i].angle >= 720 && bullets[i].angle < 752) { bullets[i].frame = 7; bullets[i].hFlip = TRUE; bullets[i].vFlip = TRUE; } - else if(bullets[i].angle >= 752 && bullets[i].angle < 784) { bullets[i].frame = 8; bullets[i].hFlip = TRUE; bullets[i].vFlip = TRUE; } - - // 768 - 1024 - else if(bullets[i].angle >= 784 && bullets[i].angle < 816) { bullets[i].frame = 7; bullets[i].vFlip = TRUE; } - else if(bullets[i].angle >= 816 && bullets[i].angle < 848) { bullets[i].frame = 6; bullets[i].vFlip = TRUE; } - else if(bullets[i].angle >= 848 && bullets[i].angle < 880) { bullets[i].frame = 5; bullets[i].vFlip = TRUE; } - else if(bullets[i].angle >= 880 && bullets[i].angle < 912) { bullets[i].frame = 4; bullets[i].vFlip = TRUE; } - else if(bullets[i].angle >= 912 && bullets[i].angle < 944) { bullets[i].frame = 3; bullets[i].vFlip = TRUE; } - else if(bullets[i].angle >= 944 && bullets[i].angle < 976) { bullets[i].frame = 2; bullets[i].vFlip = TRUE; } - else if(bullets[i].angle >= 976 && bullets[i].angle < 1008) { bullets[i].frame = 1; bullets[i].vFlip = TRUE; } - + bullets[i].frame = frame; SPR_setFrame(bullets[i].image, bullets[i].frame); SPR_setHFlip(bullets[i].image, bullets[i].hFlip); SPR_setVFlip(bullets[i].image, bullets[i].vFlip); } } -void spawnBullet(struct bulletSpawner spawner, void(*updater)){ - if((player.recoveringClock > 0 || player.respawnClock > 0) && !spawner.player) return; - // Don't spawn if offscreen +bool spawnBullet(struct bulletSpawner spawner, void(*updater)){ + if((player.recoveringClock > 0 || player.respawnClock > 0) && !spawner.player) return FALSE; + // Don't spawn if offscreen -- enemy bullets use tighter visible-screen limit fix32 dx = getWrappedDelta(spawner.x, player.pos.x); - bool offScreenX = (dx < -CULL_LIMIT || dx > CULL_LIMIT); + fix32 cullX = spawner.player ? CULL_LIMIT : SCREEN_LIMIT; + bool offScreenX = (dx < -cullX || dx > cullX); bool offScreenY = (spawner.y < FIX32(-16) || spawner.y > GAME_H_F + FIX32(16)); - if(offScreenX || offScreenY) return; + if(offScreenX || offScreenY) return FALSE; // Find available slot, return if none s16 i = -1; @@ -74,32 +45,37 @@ void spawnBullet(struct bulletSpawner spawner, void(*updater)){ break; } } - if(i == -1) return; + if(i == -1) return FALSE; + spawner.angle = F16_normalizeAngle(spawner.angle); bullets[i].active = TRUE; bullets[i].pos.x = spawner.x; bullets[i].pos.y = spawner.y; bullets[i].angle = spawner.angle; + bullets[i].speed = spawner.speed; bullets[i].player = spawner.player; bullets[i].clock = 0; if(spawner.vel.x || spawner.vel.y){ bullets[i].vel.x = spawner.vel.x; bullets[i].vel.y = spawner.vel.y; } else { - bullets[i].vel.x = fix32Mul(fix16ToFix32(cosFix16(spawner.angle)), spawner.speed); - bullets[i].vel.y = fix32Mul(fix16ToFix32(sinFix16(spawner.angle)), spawner.speed); + bullets[i].vel.x = F32_mul(F32_cos(spawner.angle), spawner.speed); + bullets[i].vel.y = F32_mul(F32_sin(spawner.angle), spawner.speed); } bullets[i].updater = updater; bullets[i].explosion = FALSE; - bullets[i].dist = bullets[i].player ? 16 : (spawner.anim == 0 ? 4 : 7); + bullets[i].grazed = FALSE; + bullets[i].dist = bullets[i].player ? 24 : (spawner.anim == 0 ? 4 : 7); + // zero out ints array + for(s16 j = 0; j < PROP_COUNT; j++) bullets[i].ints[j] = spawner.ints[j]; - bullets[i].image = SPR_addSprite(spawner.player ? &pBulletSprite : &bulletsSprite, + bullets[i].image = SPR_addSprite(&bulletsSprite, -32, -32, - TILE_ATTR(gameOver ? PAL1 : PAL0, 0, 0, spawner.player && spawner.angle == 512 ? 1 : 0)); + TILE_ATTR(gameOver ? PAL1 : PAL0, 0, 0, 0)); if(!bullets[i].image){ bullets[i].active = FALSE; - return; + return FALSE; } bullets[i].anim = spawner.anim; @@ -108,6 +84,12 @@ void spawnBullet(struct bulletSpawner spawner, void(*updater)){ SPR_setFrame(bullets[i].image, spawner.frame); SPR_setDepth(bullets[i].image, spawner.player ? 7 : (spawner.top ? 3 : 4)); doBulletRotation(i); + return TRUE; +} + +void updateBulletVel(u8 i){ + bullets[i].vel.x = F32_mul(F32_cos(bullets[i].angle), bullets[i].speed); + bullets[i].vel.y = F32_mul(F32_sin(bullets[i].angle), bullets[i].speed); } #define BULLET_CHECK FIX32(32) @@ -119,9 +101,9 @@ static void collideWithEnemy(u8 i){ fix32 deltaY = bullets[i].pos.y - enemies[j].pos.y; if(deltaY >= -BULLET_CHECK && deltaY <= BULLET_CHECK && deltaX >= -BULLET_CHECK && deltaX <= BULLET_CHECK){ - bulletDist = getApproximatedDistance(fix32ToInt(deltaX), fix32ToInt(deltaY)); + bulletDist = getApproximatedDistance(F32_toInt(deltaX), F32_toInt(deltaY)); if(bulletDist <= bullets[i].dist){ - score += (enemies[j].ints[3] >= 0) ? 200 : 100; + score += (enemies[j].ints[3] >= 0) ? 512 : 256; killBullet(i, TRUE); killEnemy(j); sfxExplosion(); @@ -137,27 +119,54 @@ static void collideWithPlayer(u8 i){ fix32 deltaY = bullets[i].pos.y - player.pos.y; s32 dist = getApproximatedDistance( - fix32ToInt(deltaX), - fix32ToInt(deltaY)); + F32_toInt(deltaX), + F32_toInt(deltaY)); if(dist <= 4){ - // convert enemy bullet to player bullet explosion in-place - SPR_setDefinition(bullets[i].image, &pBulletSprite); - bullets[i].player = TRUE; - bullets[i].pos.x = player.pos.x; - bullets[i].pos.y = player.pos.y; - killBullet(i, TRUE); - sfxExplosion(); - player.lives--; - if(player.lives == 0){ - gameOver = TRUE; - XGM2_stop(); - } else { - player.respawnClock = 120; - SPR_setVisibility(player.image, HIDDEN); - killBullets = TRUE; - hitMessageClock = 120; - hitMessageBullet = TRUE; + // kill enemy bullet, then spawn a fresh player bullet explosion + killBullet(i, FALSE); + s16 expSlot = -1; + for(s16 j = 0; j < BULLET_COUNT; j++) if(!bullets[j].active){ expSlot = j; break; } + if(expSlot >= 0){ + bullets[expSlot].active = TRUE; + bullets[expSlot].player = TRUE; + bullets[expSlot].explosion = TRUE; + bullets[expSlot].pos.x = player.pos.x; + bullets[expSlot].pos.y = player.pos.y; + bullets[expSlot].vel.x = 0; + bullets[expSlot].vel.y = 0; + bullets[expSlot].clock = 0; + bullets[expSlot].frame = 0; + bullets[expSlot].image = SPR_addSprite(&pBulletSprite, -32, -32, TILE_ATTR(PAL0, 0, 0, 0)); + if(bullets[expSlot].image){ + SPR_setDepth(bullets[expSlot].image, 5); + SPR_setAnim(bullets[expSlot].image, 1); + SPR_setFrame(bullets[expSlot].image, 0); + SPR_setHFlip(bullets[expSlot].image, random() & 1); + } else { + bullets[expSlot].active = FALSE; + } } + sfxExplosion(); + if(!isAttract){ + player.lives--; + if(player.lives == 0){ + gameOver = TRUE; + XGM2_stop(); + sfxGameOver(); + } else { + sfxPlayerHit(); + player.respawnClock = 120; + SPR_setVisibility(player.image, HIDDEN); + killBullets = TRUE; + hitMessageClock = 120; + hitMessageBullet = TRUE; + } + } + } else if(dist <= GRAZE_RADIUS && !bullets[i].grazed){ + bullets[i].grazed = TRUE; + score += 64; + grazeCount++; + sfxGraze(); } } @@ -172,8 +181,8 @@ static void updateBulletExplosion(u8 i){ SPR_setFrame(bullets[i].image, bullets[i].frame); } s16 sx = getScreenX(bullets[i].pos.x, player.camera); - s16 sy = fix32ToInt(bullets[i].pos.y); - u8 off = bullets[i].player ? P_BULLET_OFF : BULLET_OFF; + s16 sy = F32_toInt(bullets[i].pos.y); + u8 off = BULLET_OFF; SPR_setPosition(bullets[i].image, sx - off, sy - off); } @@ -182,8 +191,8 @@ static void updateBullet(u8 i){ updateBulletExplosion(i); return; } - bullets[i].pos.x += bullets[i].vel.x; - bullets[i].pos.y += bullets[i].vel.y; + bullets[i].pos.x += bullets[i].vel.x - (player.vel.x >> 3); + bullets[i].pos.y += bullets[i].vel.y - (playerScrollVelY >> 3); if(bullets[i].pos.x >= GAME_WRAP){ bullets[i].pos.x -= GAME_WRAP; @@ -203,7 +212,7 @@ static void updateBullet(u8 i){ return; } if(offScreenBottom){ - killBullet(i, TRUE); + killBullet(i, FALSE); return; } if(bullets[i].clock > 0) bullets[i].updater(i); @@ -211,8 +220,8 @@ static void updateBullet(u8 i){ else if(!gameOver) collideWithPlayer(i); if(bullets[i].active){ s16 sx = getScreenX(bullets[i].pos.x, player.camera); - s16 sy = fix32ToInt(bullets[i].pos.y); - u8 off = bullets[i].player ? P_BULLET_OFF : BULLET_OFF; + s16 sy = F32_toInt(bullets[i].pos.y); + u8 off = BULLET_OFF; SPR_setPosition(bullets[i].image, sx - off, sy - off); bullets[i].clock++; bulletCount++; @@ -225,7 +234,7 @@ void updateBullets(){ if(killBullets){ killBullets = FALSE; for(s16 i = 0; i < BULLET_COUNT; i++) - if(bullets[i].active && !bullets[i].player) killBullet(i, TRUE); + if(bullets[i].active && !bullets[i].player && !bullets[i].explosion) killBullet(i, TRUE); } for(s16 i = 0; i < BULLET_COUNT; i++) if(bullets[i].active) updateBullet(i); diff --git a/src/chrome.h b/src/chrome.h index 25ea989..9a26e40 100644 --- a/src/chrome.h +++ b/src/chrome.h @@ -1,17 +1,20 @@ -#define MAP_I 512 -#define MAP_TILE TILE_ATTR_FULL(PAL0, 1, 0, 0, MAP_I) -#define MAP_PLAYER_TILE TILE_ATTR_FULL(PAL0, 1, 0, 0, MAP_I + 1) -#define MAP_ENEMY_TILE TILE_ATTR_FULL(PAL0, 1, 0, 0, MAP_I + 2) -#define MAP_TREASURE_TILE TILE_ATTR_FULL(PAL0, 1, 0, 0, MAP_I + 3) -#define MAP_BORDER_X_TILE TILE_ATTR_FULL(PAL0, 1, 0, 0, MAP_I + 4) +#define MAP_I 328 +#define MAP_TILE TILE_ATTR_FULL(hudPal, 1, 0, 0, MAP_I) +#define MAP_PLAYER_TILE TILE_ATTR_FULL(hudPal, 1, 0, 0, MAP_I + 1) +#define MAP_ENEMY_TILE TILE_ATTR_FULL(hudPal, 1, 0, 0, MAP_I + 2) +#define MAP_BOSS_TILE TILE_ATTR_FULL(hudPal, 1, 0, 0, MAP_I + 9) +#define MAP_TREASURE_TILE TILE_ATTR_FULL(hudPal, 1, 0, 0, MAP_I + 3) +#define MAP_BORDER_X_TILE TILE_ATTR_FULL(hudPal, 1, 0, 0, MAP_I + 4) -#define FONT_BIG_I 256 +u16 hudPal = PAL0; + +#define FONT_BIG_I 340 void bigText(char* str, u16 x, u16 y, bool shadow){ for(u8 i = 0; i < strlen(str); i++){ if(str[i] >= 48){ - VDP_setTileMapXY(BG_A, TILE_ATTR_FULL(PAL0, 1, 0, 0, (shadow ? 32 : 0) + FONT_BIG_I + str[i] - 48), x + i, y); - VDP_setTileMapXY(BG_A, TILE_ATTR_FULL(PAL0, 1, 0, 0, (shadow ? 32 : 0) + FONT_BIG_I + 16 + str[i] - 48), x + i, y + 1); + VDP_setTileMapXY(BG_A, TILE_ATTR_FULL(hudPal, 1, 0, 0, (shadow ? 32 : 0) + FONT_BIG_I + str[i] - 48), x + i, y); + VDP_setTileMapXY(BG_A, TILE_ATTR_FULL(hudPal, 1, 0, 0, (shadow ? 32 : 0) + FONT_BIG_I + 16 + str[i] - 48), x + i, y + 1); } } } @@ -31,7 +34,7 @@ s16 lastLives; static void drawLives(){ VDP_clearTileMapRect(BG_A, LIVES_X, LIVES_Y, 1, 16); for(u8 i = 0; i < (player.lives - 1); i++) - VDP_fillTileMapRectInc(BG_A, TILE_ATTR_FULL(PAL0, 1, 0, 0, LIFE_I + (i > 0 ? 2 : 0)), LIVES_X, LIVES_Y + i, 1, 2); + VDP_fillTileMapRectInc(BG_A, TILE_ATTR_FULL(hudPal, 1, 0, 0, LIFE_I + (i > 0 ? 2 : 0)), LIVES_X, LIVES_Y + i, 1, 2); lastLives = player.lives; } @@ -57,17 +60,17 @@ static void drawScore(){ void loadMap(){ VDP_fillTileMapRect(BG_A, MAP_TILE, MAP_X, MAP_Y, MAP_W, MAP_H); - VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(PAL0, 1, 0, 0, MAP_I + 4), MAP_X, MAP_Y - 1, MAP_W, 1); - VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(PAL0, 1, 0, 0, MAP_I + 5), MAP_X, MAP_Y + MAP_H, MAP_W, 1); + VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(hudPal, 1, 0, 0, MAP_I + 4), MAP_X, MAP_Y - 1, MAP_W, 1); + VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(hudPal, 1, 0, 0, MAP_I + 5), MAP_X, MAP_Y + MAP_H, MAP_W, 1); - VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(PAL0, 1, 0, 0, MAP_I + 6), MAP_X - 1, MAP_Y, 1, MAP_H); - VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(PAL0, 1, 0, 1, MAP_I + 6), MAP_X + MAP_W, MAP_Y, 1, MAP_H); + VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(hudPal, 1, 0, 0, MAP_I + 6), MAP_X - 1, MAP_Y, 1, MAP_H); + VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(hudPal, 1, 0, 1, MAP_I + 6), MAP_X + MAP_W, MAP_Y, 1, MAP_H); - VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(PAL0, 1, 0, 0, MAP_I + 7), MAP_X - 1, MAP_Y + MAP_H, 1, 1); - VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(PAL0, 1, 0, 1, MAP_I + 7), MAP_X + MAP_W, MAP_Y + MAP_H, 1, 1); + VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(hudPal, 1, 0, 0, MAP_I + 7), MAP_X - 1, MAP_Y + MAP_H, 1, 1); + VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(hudPal, 1, 0, 1, MAP_I + 7), MAP_X + MAP_W, MAP_Y + MAP_H, 1, 1); - VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(PAL0, 1, 0, 0, MAP_I + 8), MAP_X - 1, MAP_Y - 1, 1, 1); - VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(PAL0, 1, 0, 1, MAP_I + 8), MAP_X + MAP_W, MAP_Y - 1, 1, 1); + VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(hudPal, 1, 0, 0, MAP_I + 8), MAP_X - 1, MAP_Y - 1, 1, 1); + VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(hudPal, 1, 0, 1, MAP_I + 8), MAP_X + MAP_W, MAP_Y - 1, 1, 1); for(s16 i = 0; i < ENEMY_COUNT; i++){ mapEnemyCol[i] = -1; @@ -96,7 +99,7 @@ static bool mapTileOccupied(s16 col, s16 row, s16 pRow){ static void updateMap(){ // compute new player row - s16 pRow = fix32ToInt(player.pos.y) / 75; + s16 pRow = F32_toInt(player.pos.y) / 75; if(pRow < 0) pRow = 0; if(pRow >= MAP_H) pRow = MAP_H - 1; @@ -109,10 +112,10 @@ static void updateMap(){ continue; } fix32 dx = getWrappedDelta(enemies[i].pos.x, player.pos.x); - s16 col = fix32ToInt(dx) / 54 + MAP_W / 2; + s16 col = F32_toInt(dx) / MAP_SCALE + MAP_W / 2; if(col < 0) col = 0; if(col >= MAP_W) col = MAP_W - 1; - s16 row = fix32ToInt(enemies[i].pos.y) / 75; + s16 row = F32_toInt(enemies[i].pos.y) / 75; if(row < 0) row = 0; if(row >= MAP_H) row = MAP_H - 1; mapNewCol[i] = col; @@ -127,10 +130,10 @@ static void updateMap(){ continue; } fix32 dx = getWrappedDelta(treasures[i].pos.x, player.pos.x); - s16 col = fix32ToInt(dx) / 54 + MAP_W / 2; + s16 col = F32_toInt(dx) / MAP_SCALE + MAP_W / 2; if(col < 0) col = 0; if(col >= MAP_W) col = MAP_W - 1; - s16 row = fix32ToInt(treasures[i].pos.y) / 75; + s16 row = F32_toInt(treasures[i].pos.y) / 75; if(row < 0) row = 0; if(row >= MAP_H) row = MAP_H - 1; mapNewTreasureCol[i] = col; @@ -173,7 +176,8 @@ static void updateMap(){ mapEnemyRow[i] = mapNewRow[i]; if(mapNewCol[i] < 0) continue; if(mapNewCol[i] == MAP_W / 2 && mapNewRow[i] == pRow) continue; - VDP_setTileMapXY(BG_A, MAP_ENEMY_TILE, MAP_X + mapNewCol[i], MAP_Y + mapNewRow[i]); + u16 eTile = (enemies[i].type == ENEMY_TYPE_BOSS) ? MAP_BOSS_TILE : MAP_ENEMY_TILE; + VDP_setTileMapXY(BG_A, eTile, MAP_X + mapNewCol[i], MAP_Y + mapNewRow[i]); } // draw player dot on top @@ -185,29 +189,68 @@ u8 phraseIndex[4]; s16 lastLevel; static void drawLevel(){ + if(isAttract) return; char lvlStr[4]; uintToStr(level + 1, lvlStr, 1); - VDP_drawText("LVL", 1, 8); - VDP_drawText(lvlStr, 4, 8); + VDP_setTextPalette(hudPal); + // VDP_drawText(lvlStr, 1, 7); + VDP_setTextPalette(PAL0); lastLevel = level; } +static void repaintMap(){ + VDP_fillTileMapRect(BG_A, MAP_TILE, MAP_X, MAP_Y, MAP_W, MAP_H); + VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(hudPal, 1, 0, 0, MAP_I + 4), MAP_X, MAP_Y - 1, MAP_W, 1); + VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(hudPal, 1, 0, 0, MAP_I + 5), MAP_X, MAP_Y + MAP_H, MAP_W, 1); + VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(hudPal, 1, 0, 0, MAP_I + 6), MAP_X - 1, MAP_Y, 1, MAP_H); + VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(hudPal, 1, 0, 1, MAP_I + 6), MAP_X + MAP_W, MAP_Y, 1, MAP_H); + VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(hudPal, 1, 0, 0, MAP_I + 7), MAP_X - 1, MAP_Y + MAP_H, 1, 1); + VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(hudPal, 1, 0, 1, MAP_I + 7), MAP_X + MAP_W, MAP_Y + MAP_H, 1, 1); + VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(hudPal, 1, 0, 0, MAP_I + 8), MAP_X - 1, MAP_Y - 1, 1, 1); + VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(hudPal, 1, 0, 1, MAP_I + 8), MAP_X + MAP_W, MAP_Y - 1, 1, 1); + // redraw tracked dots + for(s16 i = 0; i < TREASURE_COUNT; i++) + if(mapTreasureCol[i] >= 0) + VDP_setTileMapXY(BG_A, MAP_TREASURE_TILE, MAP_X + mapTreasureCol[i], MAP_Y + mapTreasureRow[i]); + for(s16 i = 0; i < ENEMY_COUNT; i++) + if(mapEnemyCol[i] >= 0){ + u16 eTile = (enemies[i].type == ENEMY_TYPE_BOSS) ? MAP_BOSS_TILE : MAP_ENEMY_TILE; + VDP_setTileMapXY(BG_A, eTile, MAP_X + mapEnemyCol[i], MAP_Y + mapEnemyRow[i]); + } + if(mapPlayerRow >= 0) + VDP_setTileMapXY(BG_A, MAP_PLAYER_TILE, MAP_X + MAP_W / 2, MAP_Y + mapPlayerRow); +} + +static void repaintHud(){ + bigText(scoreStr, SCORE_X, SCORE_Y, FALSE); + drawLives(); + repaintMap(); + drawLevel(); +} + void loadChrome(){ - VDP_loadTileSet(imageFontBig.tileset, FONT_BIG_I, DMA); + VDP_loadTileSet(imageFontBigger.tileset, FONT_BIG_I, DMA); VDP_loadTileSet(imageFontBigShadow.tileset, FONT_BIG_I + 32, DMA); VDP_loadTileSet(imageChromeLife.tileset, LIFE_I, DMA); VDP_loadTileSet(imageChromeLife2.tileset, LIFE_I + 2, DMA); VDP_loadTileSet(mapIndicator.tileset, MAP_I, DMA); lastScore = 1; - drawScore(); - drawLives(); + if(!isAttract) drawScore(); + if(!isAttract) drawLives(); drawLevel(); } bool didGameOver; u32 gameOverClock; +static bool gameOverFading; static void doGameOver(){ didGameOver = TRUE; + // check and save high score + if(score > highScore){ + highScore = score; + saveHighScore(); + VDP_drawText("NEW HIGH SCORE!", 14, 15); + } for(s16 i = 0; i < BULLET_COUNT; i++) if(bullets[i].active) SPR_setPalette(bullets[i].image, PAL1); for(s16 i = 0; i < ENEMY_COUNT; i++) if(enemies[i].active) SPR_setPalette(enemies[i].image, PAL1); for(s16 i = 0; i < TREASURE_COUNT; i++) if(treasures[i].active){ @@ -238,19 +281,28 @@ static void doGameOver(){ treasureCollectedClock = 0; allTreasureCollected = FALSE; hitMessageClock = 0; - VDP_clearText(9, 5, 22); + VDP_clearText(9, 5, 23); - VDP_drawText("GAME OVER", 15, 13); - VDP_drawText("PRESS ANY BUTTON", 12, 14); + hudPal = PAL1; + hudPal = PAL1; + repaintHud(); + + VDP_drawText("GAME OVER", 15, 14); + VDP_drawText("Press Any Button", 12, 16); } +#define PAUSE_Y 15 + static void showPause(){ for(s16 i = 0; i < BULLET_COUNT; i++) if(bullets[i].active) SPR_setPalette(bullets[i].image, PAL1); for(s16 i = 0; i < ENEMY_COUNT; i++) if(enemies[i].active) SPR_setPalette(enemies[i].image, PAL1); for(s16 i = 0; i < TREASURE_COUNT; i++) if(treasures[i].active) SPR_setPalette(treasures[i].image, PAL1); SPR_setPalette(player.image, PAL1); + hudPal = PAL1; + hudPal = PAL1; + repaintHud(); XGM2_pause(); - VDP_drawText("PAUSE", 17, 13); + VDP_drawText("PAUSED", 17, PAUSE_Y); } static void clearPause(){ @@ -258,13 +310,15 @@ static void clearPause(){ for(s16 i = 0; i < ENEMY_COUNT; i++) if(enemies[i].active) SPR_setPalette(enemies[i].image, PAL0); for(s16 i = 0; i < TREASURE_COUNT; i++) if(treasures[i].active) SPR_setPalette(treasures[i].image, PAL0); SPR_setPalette(player.image, PAL0); + hudPal = PAL0; + repaintHud(); XGM2_resume(); - VDP_clearText(17, 13, 5); + VDP_clearText(17, PAUSE_Y, 6); } u32 pauseClock; static void updatePause(){ - if(gameOver) return; + if(gameOver || isAttract || levelWaitClock > 0 || levelClearing) return; if(ctrl.start){ if(!isPausing){ isPausing = TRUE; @@ -282,59 +336,90 @@ static void updatePause(){ } if(paused){ if(pauseClock % 60 < 30) - VDP_drawText("PAUSE", 17, 13); + VDP_drawText("PAUSED", 17, PAUSE_Y); else - VDP_clearText(17, 13, 5); + VDP_clearText(17, PAUSE_Y, 6); pauseClock++; if(pauseClock >= 240) pauseClock = 0; } } +#define TRANSITION_TREASURE_X 10 +#define TRANSITION_TREASURE_Y 13 + +#define TRANSITION_LEVEL_X 12 +#define TRANSITION_LEVEL_Y 15 + void updateChrome(){ updatePause(); if(gameOver && !didGameOver) doGameOver(); if(didGameOver){ gameOverClock++; - if((gameOverClock > 120 && (ctrl.a || ctrl.b || ctrl.c || ctrl.start)) || gameOverClock > 900) + if(!gameOverFading){ + if((gameOverClock > 120 && (ctrl.a || ctrl.b || ctrl.c || ctrl.start)) || gameOverClock > 900){ + gameOverFading = TRUE; + PAL_fadeOut(0, 47, 20, TRUE); + } + } else if(!PAL_isDoingFade()){ SYS_hardReset(); + } return; } // level transition overlay if(levelClearing){ if(levelClearClock == 2){ + char numStr[12]; char lvlStr[4]; - uintToStr(level + 2, lvlStr, 1); - VDP_drawText("LEVEL ", 15, 13); - VDP_drawText(lvlStr, 21, 13); + char livesStr[4]; + score += 2048 + 1024 * level; + lastScore = score; + + uintToStr(statTreasures, numStr, 1); + VDP_drawText("Collected", TRANSITION_TREASURE_X, TRANSITION_TREASURE_Y); + VDP_drawText(numStr, TRANSITION_TREASURE_X + 10, TRANSITION_TREASURE_Y); + VDP_drawText("Treasure", TRANSITION_TREASURE_X + 10 + 2, TRANSITION_TREASURE_Y); + if(statTreasures != 1) VDP_drawText("s", TRANSITION_TREASURE_X + 10 + 2 + 8, TRANSITION_TREASURE_Y); + + uintToStr(level + 1, lvlStr, 1); + VDP_drawText("Completed Level", TRANSITION_LEVEL_X, TRANSITION_LEVEL_Y); + VDP_drawText(lvlStr, TRANSITION_LEVEL_X + 16, TRANSITION_LEVEL_Y); + + uintToStr(lastScore, scoreStr, 1); + VDP_drawText("Score", TRANSITION_LEVEL_X, TRANSITION_LEVEL_Y + 3); + VDP_drawText(scoreStr, TRANSITION_LEVEL_X + 6, TRANSITION_LEVEL_Y + 3); + + uintToStr(player.lives, livesStr, 1); + VDP_drawText(livesStr, TRANSITION_LEVEL_X, TRANSITION_LEVEL_Y + 5); + if(player.lives == 1) + VDP_drawText("Life Left", TRANSITION_LEVEL_X + 2, TRANSITION_LEVEL_Y + 5); + else + VDP_drawText("Lives Left", TRANSITION_LEVEL_X + 2, TRANSITION_LEVEL_Y + 5); + } - if(levelClearClock >= 110){ - VDP_clearText(15, 13, 10); + if(levelClearClock >= 230){ + VDP_clearText(0, TRANSITION_TREASURE_Y, 40); + VDP_clearText(0, TRANSITION_LEVEL_Y, 40); + VDP_clearText(0, TRANSITION_LEVEL_Y + 3, 40); + VDP_clearText(0, TRANSITION_LEVEL_Y + 5, 40); } return; } - if(lastScore != score){ + if(!isAttract && lastScore != score){ lastScore = score; drawScore(); + // check for extend + while(score >= nextExtendScore){ + player.lives++; + nextExtendScore = (nextExtendScore * 5) / 2; // previous + previous * 1.5 + drawLives(); + } } - if(lastLives != player.lives) drawLives(); + if(!isAttract && lastLives != player.lives) drawLives(); if(lastLevel != level) drawLevel(); if(treasureCollectedClock > 0 && levelWaitClock == 0){ if(treasureCollectedClock == 120){ - VDP_clearText(10, 5, 22); - const char* mirrorPhrases[] = {"REFLECT THE DEPTHS", "DIG DEEPER WITHIN", "SEE WHAT SHINES BELOW", "MIRROR OF THE MINE", "LOOK BACK STRIKE BACK"}; - const char* lampPhrases[] = {"STRIKE LIGHT", "LET THERE BE LODE", "BRIGHT IDEA DEEP DOWN", "ILLUMINATE THE VEIN", "GLOW FROM BELOW"}; - const char* scarfPhrases[] = {"COZY IN THE CAVES", "WRAP THE UNDERWORLD", "SNUG AS BEDROCK", "STYLE FROM THE STRATA", "WARM THE DEPTHS"}; - const char* swordPhrases[] = {"ORE YOU READY", "MINED YOUR STEP", "CUTTING EDGE GEOLOGY", "STRIKE THE VEIN", "SPIRIT STEEL"}; - const char** sets[] = {mirrorPhrases, lampPhrases, scarfPhrases, swordPhrases}; - const char* phrase = sets[treasureCollectedType][phraseIndex[treasureCollectedType]]; - phraseIndex[treasureCollectedType] = (phraseIndex[treasureCollectedType] + 1) % 5; - u8 len = strlen(phrase); - VDP_drawText(phrase, 20 - len / 2, 5); - } - treasureCollectedClock--; - if(treasureCollectedClock == 0){ - VDP_clearText(10, 5, 22); - // check if all treasures are collected or gone + VDP_clearText(10, 5, 23); + // check if all treasures are now collected or gone bool allDone = TRUE; for(s16 j = 0; j < TREASURE_COUNT; j++){ if(treasures[j].active && treasures[j].state != TREASURE_COLLECTED){ @@ -344,26 +429,41 @@ void updateChrome(){ } if(allDone && collectedCount > 0){ allTreasureCollected = TRUE; - VDP_drawText("ALL TREASURE COLLECTED", 9, 5); + VDP_drawText("All Treasure Found!", 11, 5); + } else { + const char* mirrorPhrases[] = {"Reflect the Depths", "Dig Deeper Within", "See What Shines Below", "Mirror of the Mine", "Look Back, Strike Back"}; + const char* lampPhrases[] = {"Strike Light", "Let There Be Lode!", "Bright Idea Deep Down", "Illuminate the Vein", "Glow from Below"}; + const char* scarfPhrases[] = {"Cozy in the Caves", "Wrap the Underworld", "Snug as Bedrock", "Style from the Strata", "Warm the Depths"}; + const char* swordPhrases[] = {"Ore You Ready?", "Mined Your Step", "Cutting Edge Geology", "Strike the Vein", "Spirit Steel"}; + const char** sets[] = {mirrorPhrases, lampPhrases, scarfPhrases, swordPhrases}; + const char* phrase = sets[treasureCollectedType][phraseIndex[treasureCollectedType]]; + phraseIndex[treasureCollectedType] = (phraseIndex[treasureCollectedType] + 1) % 5; + u8 len = strlen(phrase); + u8 phraseX = 20 - len / 2; + if(phraseX < 10) phraseX = 10; + VDP_drawText(phrase, phraseX, 5); } } + treasureCollectedClock--; + if(treasureCollectedClock == 0) + VDP_clearText(9, 5, 24); } if(hitMessageClock > 0){ if(hitMessageClock == 120){ - VDP_clearText(9, 5, 22); + VDP_clearText(9, 5, 23); treasureCollectedClock = 0; allTreasureCollected = FALSE; - VDP_drawText(hitMessageBullet ? "BLASTED" : "SMASHED", hitMessageBullet ? 16 : 16, 5); + VDP_drawText(hitMessageBullet ? "Got You!" : "Collision!", 20 - (hitMessageBullet ? 8 : 10) / 2, 5); } hitMessageClock--; if(hitMessageClock == 0) - VDP_clearText(9, 5, 22); + VDP_clearText(9, 5, 23); } - if(levelWaitClock == 240){ - VDP_clearText(9, 5, 22); + if(levelWaitClock == 210){ + VDP_clearText(9, 5, 23); treasureCollectedClock = 0; allTreasureCollected = FALSE; - VDP_drawText("ALL ENEMIES DESTROYED", 9, 5); + VDP_drawText("All Enemies Down!", 12, 5); } if(clock % 4 == 0) updateMap(); } \ No newline at end of file diff --git a/src/enemies.h b/src/enemies.h index 8e7b3b5..ffb15b0 100644 --- a/src/enemies.h +++ b/src/enemies.h @@ -42,19 +42,27 @@ void spawnEnemy(u8 type, u8 zone){ enemies[i].pos.x = randX; enemies[i].pos.y = randY; - enemies[i].off = 16; - enemies[i].image = SPR_addSprite(&fairySprite, - getScreenX(enemies[i].pos.x, player.camera) - enemies[i].off, fix32ToInt(enemies[i].pos.y) - enemies[i].off, TILE_ATTR(PAL0, 0, 0, 0)); + static const SpriteDefinition* bossSpriteDefs[4] = { &boss1Sprite, &boss2Sprite, &boss3Sprite, &boss4Sprite }; + SpriteDefinition const* spriteDef; + if(type == ENEMY_TYPE_DRONE) spriteDef = &eyeBigSprite; + else if(type == ENEMY_TYPE_BOSS) spriteDef = bossSpriteDefs[pendingBossNum % 4]; + else spriteDef = &fairySprite; + enemies[i].off = (type == ENEMY_TYPE_BOSS) ? 24 : 16; + enemies[i].image = SPR_addSprite(spriteDef, + getScreenX(enemies[i].pos.x, player.camera) - enemies[i].off, F32_toInt(enemies[i].pos.y) - enemies[i].off, TILE_ATTR(PAL0, 0, 0, 0)); if(!enemies[i].image){ enemies[i].active = FALSE; return; } + SPR_setDepth(enemies[i].image, (type == ENEMY_TYPE_BOSS) ? 1 : 2); SPR_setVisibility(enemies[i].image, HIDDEN); enemies[i].hp = 1; for(u8 j = 0; j < PROP_COUNT; j++){ enemies[i].ints[j] = 0; + enemies[i].fixes[j] = 0; } enemies[i].ints[3] = -1; + enemies[i].anim = 0; switch(enemies[i].type){ case ENEMY_TYPE_TEST: loadEnemyOne(i); @@ -75,9 +83,9 @@ void spawnEnemy(u8 type, u8 zone){ loadBoss(i); break; } - enemies[i].vel.x = fix32Mul(fix16ToFix32(cosFix16(enemies[i].angle)), enemies[i].speed); - enemies[i].vel.y = fix32Mul(fix16ToFix32(sinFix16(enemies[i].angle)), enemies[i].speed); - + enemies[i].vel.x = F32_mul(F32_cos(enemies[i].angle), enemies[i].speed); + enemies[i].vel.y = F32_mul(F32_sin(enemies[i].angle), enemies[i].speed); + SPR_setAnim(enemies[i].image, enemies[i].anim); } static void boundsEnemy(u8 i){ @@ -92,21 +100,39 @@ static void boundsEnemy(u8 i){ } // carrying: only check for reaching the top else if(enemies[i].pos.y <= FIX32(0)){ - if(treasures[h].active) killTreasure(h); - enemies[i].ints[3] = -1; - treasureBeingCarried = FALSE; - if(enemies[i].type == ENEMY_TYPE_BUILDER){ - u8 zone = fix32ToInt(enemies[i].pos.x) / 512; - spawnEnemy(ENEMY_TYPE_GUNNER, zone); + if(isAttract){ + // in attract mode enemies can't die -- drop treasure and head back down + if(treasures[h].active){ + treasures[h].state = TREASURE_FALLING; + treasures[h].carriedBy = -1; + treasures[h].vel.x = 0; + treasures[h].vel.y = FIX32(3); + } + enemies[i].ints[3] = -1; + treasureBeingCarried = FALSE; + enemies[i].vel.y = FIX32(1); + } else { + if(treasures[h].active) killTreasure(h); + enemies[i].ints[3] = -1; + treasureBeingCarried = FALSE; + if(enemies[i].type == ENEMY_TYPE_BUILDER){ + u8 zone = F32_toInt(enemies[i].pos.x) / 512; + spawnEnemy(ENEMY_TYPE_GUNNER, zone); + } + enemies[i].hp = 0; + killEnemy(i); } - enemies[i].hp = 0; - killEnemy(i); return; } } else { // not carrying: bounce off top and bottom - if(enemies[i].pos.y >= GAME_H_F - FIX32(enemies[i].off) || enemies[i].pos.y <= FIX32(enemies[i].off)) - enemies[i].vel.y *= -1; + if(enemies[i].pos.y >= GAME_H_F - FIX32(enemies[i].off)){ + if(enemies[i].vel.y > 0) enemies[i].vel.y = -enemies[i].vel.y; + enemies[i].pos.y = GAME_H_F - FIX32(enemies[i].off); + } else if(enemies[i].pos.y <= FIX32(enemies[i].off)){ + if(enemies[i].vel.y < 0) enemies[i].vel.y = -enemies[i].vel.y; + enemies[i].pos.y = FIX32(enemies[i].off); + } } if(enemies[i].pos.x >= GAME_WRAP){ @@ -118,8 +144,8 @@ static void boundsEnemy(u8 i){ } static void updateEnemy(u8 i){ - enemies[i].pos.x += enemies[i].vel.x; - enemies[i].pos.y += enemies[i].vel.y; + enemies[i].pos.x += enemies[i].vel.x - (player.vel.x >> 3); + enemies[i].pos.y += enemies[i].vel.y - (playerScrollVelY >> 3); boundsEnemy(i); if(!enemies[i].active) return; @@ -169,6 +195,7 @@ static void updateEnemy(u8 i){ bullets[expSlot].frame = 0; bullets[expSlot].image = SPR_addSprite(&pBulletSprite, -32, -32, TILE_ATTR(PAL0, 0, 0, 0)); if(bullets[expSlot].image){ + SPR_setDepth(bullets[expSlot].image, 5); SPR_setAnim(bullets[expSlot].image, 1); SPR_setFrame(bullets[expSlot].image, 0); SPR_setHFlip(bullets[expSlot].image, random() & 1); @@ -180,24 +207,29 @@ static void updateEnemy(u8 i){ enemies[i].hp = 0; killEnemy(i); } - player.lives--; - if(player.lives == 0){ - gameOver = TRUE; - XGM2_stop(); - } else { - player.respawnClock = 120; - SPR_setVisibility(player.image, HIDDEN); - killBullets = TRUE; - hitMessageClock = 120; - hitMessageBullet = FALSE; + if(!isAttract){ + player.lives--; + if(player.lives == 0){ + gameOver = TRUE; + XGM2_stop(); + sfxGameOver(); + } else { + sfxPlayerHit(); + player.respawnClock = 120; + SPR_setVisibility(player.image, HIDDEN); + killBullets = TRUE; + hitMessageClock = 120; + hitMessageBullet = FALSE; + } } } } s16 sx = getScreenX(enemies[i].pos.x, player.camera); - s16 sy = fix32ToInt(enemies[i].pos.y); + s16 sy = F32_toInt(enemies[i].pos.y); SPR_setVisibility(enemies[i].image, enemies[i].onScreen ? VISIBLE : HIDDEN); - SPR_setHFlip(enemies[i].image, enemies[i].vel.x > 0); + if(enemies[i].type != ENEMY_TYPE_DRONE && enemies[i].type != ENEMY_TYPE_BOSS) + SPR_setHFlip(enemies[i].image, enemies[i].vel.x > 0); SPR_setPosition(enemies[i].image, sx - enemies[i].off, sy - enemies[i].off); enemies[i].clock++; diff --git a/src/enemytypes.h b/src/enemytypes.h index e5078bc..7c38add 100644 --- a/src/enemytypes.h +++ b/src/enemytypes.h @@ -1,41 +1,150 @@ -// ============================================================================= -// --- Type 0: Test / Fairy (EnemyOne) --- -// ============================================================================= -// The original enemy type. A fairy that shoots 8-bullet circular bursts and -// also seeks/abducts treasures (same carry behavior as Builder). -// -// Behavior: -// - When NOT carrying: drifts at speed 2, shoots 8 bullets in a circle -// every 20 frames (only when on screen). Also scans for nearby walking -// treasures every 30 frames and steers toward the closest one within 256px. -// Grabs the treasure when within 16px. -// - When carrying: flies upward (angle 704-832, roughly up-left to up-right) -// at speed 2. Skips all shooting. boundsEnemy() handles reaching the top -// (kills treasure, self-destructs -- does NOT spawn a Gunner unlike Builder). -// -// ints[0] = random shot timer offset (0-59), desynchronizes shooting from -// other enemies so they don't all fire on the same frame -// ints[2] = target treasure index (-1 = no target) -// ints[3] = carried treasure index (-1 = not carrying) -// -// Speed: 2 HP: 1 Shoots: yes (8-bullet radial, every 20 frames) -// Abducts: yes (only 1 treasure globally at a time via treasureBeingCarried flag) -// ============================================================================= +static u8 getBossPhase(u8 i, u8 numPhases){ + s16 maxHp = enemies[i].ints[4]; + if(maxHp <= 0) return 0; + s16 lost = maxHp - enemies[i].hp; + if(lost < 0) lost = 0; + u8 phase = (lost * numPhases) / maxHp; + if(phase >= numPhases) phase = numPhases - 1; + return phase; +} + +static fix16 enemyHoneAngle(u8 i){ + fix32 dx = getWrappedDelta(player.pos.x, enemies[i].pos.x); + fix32 dy = player.pos.y - enemies[i].pos.y; + return getAngle(dx, dy); +} + +// drone -- aim travel with triad shot +void loadDrone(u8 i){ + enemies[i].ints[0] = random() % 60; + enemies[i].angle = FIX16(random() % 360); + enemies[i].speed = FIX32(1 + (random() % 2)); + enemies[i].anim = random() % 3; +} +static void moveDrone(u8 i){ + enemies[i].ints[1]++; + if(enemies[i].ints[1] != 30) return; + enemies[i].ints[1] = 0; + enemies[i].angle = enemyHoneAngle(i); + if(player.respawnClock > 0) enemies[i].angle = F16_normalizeAngle(enemies[i].angle + FIX16(180)); + enemies[i].vel.x = F32_mul(F32_cos(enemies[i].angle), enemies[i].speed); + enemies[i].vel.y = F32_mul(F32_sin(enemies[i].angle), enemies[i].speed); + SPR_setFrame(enemies[i].image, (u8)((F16_toInt(enemies[i].angle) / 45 + 1) % 8)); +} +static void shootDrone(u8 i){ + if(level == 0) return; + if(!enemies[i].onScreen || enemies[i].clock % 60 != (u32)(enemies[i].ints[0])) return; + sfxEnemyShotC(); + struct bulletSpawner spawner = { + .x = enemies[i].pos.x, + .y = enemies[i].pos.y, + .anim = 0, + .speed = FIX32(3), + .angle = FIX16(random() % 360), + }; + + if(level >= 12){ + spawner.anim = 1; + spawner.speed = FIX32(4.5); + for(u8 j = 0; j < 4; j++){ + spawnBullet(spawner, EMPTY); + spawner.angle = angleAdd(spawner.angle, FIX16(90)); + } + } else if(level >= 9){ + spawner.anim = 1; + spawner.speed = FIX32(4); + for(u8 j = 0; j < 4; j++){ + spawnBullet(spawner, EMPTY); + spawner.angle = angleAdd(spawner.angle, FIX16(90)); + } + } else if(level >= 6){ + spawner.anim = 1; + spawner.speed = FIX32(3.5); + for(u8 j = 0; j < 4; j++){ + spawnBullet(spawner, EMPTY); + spawner.angle = angleAdd(spawner.angle, FIX16(90)); + } + } else if(level >= 3){ + spawner.anim = 1; + spawner.speed = FIX32(3.5); + for(u8 j = 0; j < 3; j++){ + spawnBullet(spawner, EMPTY); + spawner.angle = angleAdd(spawner.angle, FIX16(120)); + } + } else { + for(u8 j = 0; j < 3; j++){ + spawnBullet(spawner, EMPTY); + spawner.angle = angleAdd(spawner.angle, FIX16(120)); + } + } + + + +} +void updateDrone(u8 i){ + moveDrone(i); + shootDrone(i); +} + +// gunner -- drifts with aim burst +void loadGunner(u8 i){ + enemies[i].ints[0] = random() % 2; + enemies[i].ints[1] = random() % 40; + enemies[i].angle = FIX16((random() % 4) * 90 + 45); + enemies[i].speed = FIX32(2); + enemies[i].hp = 2; +} +void updateGunner(u8 i){ + if(!enemies[i].onScreen || enemies[i].clock % 40 != (u32)(enemies[i].ints[1])) return; + sfxEnemyShotA(); + fix16 aimAngle = enemyHoneAngle(i); + struct bulletSpawner spawner = { + .x = enemies[i].pos.x, + .y = enemies[i].pos.y, + .anim = 6, + .speed = FIX32(4), + .angle = aimAngle - FIX16(28), + }; + for(u8 j = 0; j < 3; j++){ + spawnBullet(spawner, EMPTY); + spawner.angle = angleAdd(spawner.angle, FIX16(28)); + } +} + +// boss one +// static void bossPatternOne(u8 i){ +// if(enemies[i].clock % 60 < 30 && enemies[i].clock % 10 == 0){ +// if(enemies[i].clock % 60 == 0) enemies[i].ints[1] = FIX16(random() % 9); +// struct bulletSpawner spawner = { +// .x = enemies[i].pos.x, +// .y = enemies[i].pos.y, +// .anim = enemies[i].clock % 20 == 0 ? 0 : 1, +// .speed = FIX32(3), +// .angle = enemies[i].ints[1] +// }; +// for(u8 j = 0; j < 6; j++){ +// spawnBullet(spawner, EMPTY); +// spawner.angle = angleAdd(spawner.angle, FIX16(60)); +// } +// enemies[i].ints[1] = angleAdd(enemies[i].ints[1], FIX16(9)); +// sfxEnemyShotA(); +// } +// } + +// test enemy with grabbin void loadEnemyOne(u8 i){ enemies[i].ints[0] = random() % 60; enemies[i].ints[2] = -1; // target treasure index enemies[i].ints[3] = -1; // carried treasure index - enemies[i].angle = ((random() % 4) * 256) + 128; + enemies[i].angle = FIX16((random() % 4) * 90 + 45); enemies[i].speed = FIX32(2); } void updateEnemyOne(u8 i){ // carrying behavior: move upward, skip shooting if(enemies[i].ints[3] >= 0){ - // enemies[i].vel.x = (enemies[i].vel.x > 0) ? FIX32(0.3) : FIX32(-0.3); - // enemies[i].vel.y = FIX32(-1.5); - enemies[i].angle = 704 + (random() % 128); - enemies[i].vel.x = fix32Mul(fix16ToFix32(cosFix16(enemies[i].angle)), enemies[i].speed); - enemies[i].vel.y = fix32Mul(fix16ToFix32(sinFix16(enemies[i].angle)), enemies[i].speed); + enemies[i].angle = FIX16(248 + (random() % 45)); + enemies[i].vel.x = F32_mul(F32_cos(enemies[i].angle), enemies[i].speed); + enemies[i].vel.y = F32_mul(F32_sin(enemies[i].angle), enemies[i].speed); return; } @@ -71,11 +180,9 @@ void updateEnemyOne(u8 i){ fix32 dy = treasures[t].pos.y - enemies[i].pos.y; // hone toward treasure's current position at base speed - s16 angle = honeAngle( - fix32ToFix16(enemies[i].pos.x), fix32ToFix16(treasures[t].pos.x), - fix32ToFix16(enemies[i].pos.y), fix32ToFix16(treasures[t].pos.y)); - enemies[i].vel.x = fix32Mul(fix16ToFix32(cosFix16(angle)), enemies[i].speed); - enemies[i].vel.y = fix32Mul(fix16ToFix32(sinFix16(angle)), enemies[i].speed); + fix16 angle = getAngle(dx, dy); + enemies[i].vel.x = F32_mul(F32_cos(angle), enemies[i].speed); + enemies[i].vel.y = F32_mul(F32_sin(angle), enemies[i].speed); // grab check: within 16px fix32 adx = dx < 0 ? -dx : dx; @@ -99,7 +206,7 @@ void updateEnemyOne(u8 i){ .y = enemies[i].pos.y, .anim = 3 + (random() % 3), .speed = FIX32(4) + FIX32(random() % 4), - .angle = random() % 128, + .angle = FIX16(random() % 45), }; switch(random() % 3){ case 1: spawner.anim += 3; break; @@ -107,149 +214,7 @@ void updateEnemyOne(u8 i){ } for(u8 j = 0; j < 8; j++){ spawnBullet(spawner, EMPTY); - spawner.angle += 128; - } - } -} - -// ============================================================================= -// --- Type 1: Drone --- -// ============================================================================= -// Bread-and-butter pressure enemy. Periodically recalculates heading toward -// the player and fires single aimed bullets. The main "fodder" type -- use -// high counts (8-16) to create constant movement pressure without overwhelming -// bullet density. -// -// Behavior: -// - Recalculates heading toward player every 30 frames (ints[1] counter). -// Between recalcs, travels in a straight line at speed 2. -// - Fires 1 aimed bullet at player every 40 frames. Only shoots when: -// a) on screen, AND b) level index >= 1 (i.e. L2+, so L1 drones are -// harmless). This is hardcoded, separate from LevelDef.dronesShoot. -// - Bounces off top/bottom screen edges. -// -// ints[0] = random shot timer offset (0-59), prevents synchronized volleys -// ints[1] = heading recalculation timer (counts up to 30) -// -// Speed: 2 HP: 1 Shoots: yes (1 aimed bullet, every 40 frames, L2+ only) -// Abducts: no -// ============================================================================= -void loadDrone(u8 i){ - enemies[i].ints[0] = random() % 60; - enemies[i].ints[1] = 0; - enemies[i].angle = random() % 1024; - enemies[i].speed = FIX32(2); -} -void updateDrone(u8 i){ - // recalculate heading toward player every 30 frames - enemies[i].ints[1]++; - if(enemies[i].ints[1] >= 30){ - enemies[i].ints[1] = 0; - fix32 dx = getWrappedDelta(player.pos.x, enemies[i].pos.x); - fix32 dy = player.pos.y - enemies[i].pos.y; - enemies[i].angle = honeAngle( - fix32ToFix16(enemies[i].pos.x), fix32ToFix16(enemies[i].pos.x) + fix32ToFix16(dx), - fix32ToFix16(enemies[i].pos.y), fix32ToFix16(enemies[i].pos.y) + fix32ToFix16(dy)); - if(player.respawnClock > 0) enemies[i].angle = (enemies[i].angle + 512) % 1024; - enemies[i].vel.x = fix32Mul(fix16ToFix32(cosFix16(enemies[i].angle)), enemies[i].speed); - enemies[i].vel.y = fix32Mul(fix16ToFix32(sinFix16(enemies[i].angle)), enemies[i].speed); - } - // shooting: 1 aimed bullet every ~40 frames (only if level >= 1 i.e. L2+) - if(level >= 1 && enemies[i].onScreen && - enemies[i].clock % 40 == (u32)(enemies[i].ints[0]) % 40){ - sfxEnemyShotC(); - fix32 dx = getWrappedDelta(player.pos.x, enemies[i].pos.x); - fix32 dy = player.pos.y - enemies[i].pos.y; - s16 aimAngle = honeAngle( - fix32ToFix16(enemies[i].pos.x), fix32ToFix16(enemies[i].pos.x) + fix32ToFix16(dx), - fix32ToFix16(enemies[i].pos.y), fix32ToFix16(enemies[i].pos.y) + fix32ToFix16(dy)); - if(player.respawnClock > 0) aimAngle = (aimAngle + 512) % 1024; - struct bulletSpawner spawner = { - .x = enemies[i].pos.x, - .y = enemies[i].pos.y, - .anim = 3 + (random() % 9), - .speed = FIX32(4), - .angle = aimAngle, - }; - spawnBullet(spawner, EMPTY); - } -} - -// ============================================================================= -// --- Type 2: Gunner --- -// ============================================================================= -// Danmaku / bullet geometry enemy. Drifts very slowly (speed 0.5), acting more -// like a turret than a chaser. Only fires when on screen. The bullet pattern is -// set by LevelDef.gunnerPattern (written to ints[0] by loadLevel). -// -// Behavior: -// - Drifts at speed 0.5 in a random initial direction. Bounces off top/bottom. -// - Does nothing when off screen (early return). -// - Pattern 0 (Radial Burst): fires 8 bullets in a circle every 60 frames. -// Start angle rotates by 24 each volley, creating a spiral-over-time effect. -// Bullet speed 3. Predictable, good for learning dodge patterns. -// - Pattern 1 (Aimed Fan): fires 5 bullets aimed at player, spread across -// +-64 angle units, every 45 frames. Bullet speed 3. More aggressive and -// targeted -- harder to dodge at close range. -// - Pattern is set per-gunner at level load. gunnerPattern=2 means each -// gunner randomly picks 0 or 1, creating mixed bullet fields. -// -// ints[0] = pattern type (0=radial, 1=aimed fan). Set by loadLevel, not random. -// ints[1] = random shot timer offset (0-59) -// ints[2] = angle accumulator (radial pattern only, rotates start angle) -// -// Speed: 0.5 HP: 1 Shoots: yes (pattern-dependent, see above) -// Abducts: no -// ============================================================================= -void loadGunner(u8 i){ - enemies[i].ints[0] = random() % 2; - enemies[i].ints[1] = random() % 60; - enemies[i].ints[2] = 0; - enemies[i].angle = random() % 1024; - enemies[i].speed = FIX32(0.5); -} -void updateGunner(u8 i){ - if(!enemies[i].onScreen) return; - if(enemies[i].ints[0] == 0){ - // Pattern 0: Radial Burst - 8 bullets every ~60 frames - if(enemies[i].clock % 60 == (u32)(enemies[i].ints[1]) % 60){ - sfxEnemyShotB(); - s16 baseAngle = enemies[i].ints[2]; - struct bulletSpawner spawner = { - .x = enemies[i].pos.x, - .y = enemies[i].pos.y, - .anim = 3 + (random() % 3), - .speed = FIX32(3), - .angle = baseAngle, - }; - for(u8 j = 0; j < 8; j++){ - spawnBullet(spawner, EMPTY); - spawner.angle += 128; - } - enemies[i].ints[2] += 24; - if(enemies[i].ints[2] >= 1024) enemies[i].ints[2] -= 1024; - } - } else { - // Pattern 1: Aimed Fan - 5 bullets spread +-64 every ~45 frames - if(enemies[i].clock % 45 == (u32)(enemies[i].ints[1]) % 45){ - sfxEnemyShotA(); - fix32 dx = getWrappedDelta(player.pos.x, enemies[i].pos.x); - fix32 dy = player.pos.y - enemies[i].pos.y; - s16 aimAngle = honeAngle( - fix32ToFix16(enemies[i].pos.x), fix32ToFix16(enemies[i].pos.x) + fix32ToFix16(dx), - fix32ToFix16(enemies[i].pos.y), fix32ToFix16(enemies[i].pos.y) + fix32ToFix16(dy)); - if(player.respawnClock > 0) aimAngle = (aimAngle + 512) % 1024; - struct bulletSpawner spawner = { - .x = enemies[i].pos.x, - .y = enemies[i].pos.y, - .anim = 6 + (random() % 3), - .speed = FIX32(3), - .angle = aimAngle - 64, - }; - for(u8 j = 0; j < 5; j++){ - spawnBullet(spawner, EMPTY); - spawner.angle += 32; - } + spawner.angle = angleAdd(spawner.angle, FIX16(45)); } } } @@ -279,18 +244,14 @@ void updateGunner(u8 i){ // - Hunters are the anti-camping enemy: you can't sit still // ============================================================================= void loadHunter(u8 i){ - enemies[i].angle = random() % 1024; + enemies[i].angle = FIX16(random() % 360); enemies[i].speed = FIX32(5); } void updateHunter(u8 i){ - fix32 dx = getWrappedDelta(player.pos.x, enemies[i].pos.x); - fix32 dy = player.pos.y - enemies[i].pos.y; - enemies[i].angle = honeAngle( - fix32ToFix16(enemies[i].pos.x), fix32ToFix16(enemies[i].pos.x) + fix32ToFix16(dx), - fix32ToFix16(enemies[i].pos.y), fix32ToFix16(enemies[i].pos.y) + fix32ToFix16(dy)); - if(player.respawnClock > 0) enemies[i].angle = (enemies[i].angle + 512) % 1024; - enemies[i].vel.x = fix32Mul(fix16ToFix32(cosFix16(enemies[i].angle)), enemies[i].speed); - enemies[i].vel.y = fix32Mul(fix16ToFix32(sinFix16(enemies[i].angle)), enemies[i].speed); + enemies[i].angle = enemyHoneAngle(i); + if(player.respawnClock > 0) enemies[i].angle = F16_normalizeAngle(enemies[i].angle + FIX16(180)); + enemies[i].vel.x = F32_mul(F32_cos(enemies[i].angle), enemies[i].speed); + enemies[i].vel.y = F32_mul(F32_sin(enemies[i].angle), enemies[i].speed); } // ============================================================================= @@ -330,16 +291,16 @@ void loadBuilder(u8 i){ enemies[i].ints[0] = random() % 60; enemies[i].ints[2] = -1; enemies[i].ints[3] = -1; - enemies[i].angle = random() % 1024; + enemies[i].angle = FIX16(random() % 360); enemies[i].speed = FIX32(0.7); } void updateBuilder(u8 i){ // carrying: steer upward if(enemies[i].ints[3] >= 0){ - enemies[i].angle = 704 + (random() % 128); + enemies[i].angle = FIX16(248 + (random() % 45)); enemies[i].speed = FIX32(1.4); - enemies[i].vel.x = fix32Mul(fix16ToFix32(cosFix16(enemies[i].angle)), enemies[i].speed); - enemies[i].vel.y = fix32Mul(fix16ToFix32(sinFix16(enemies[i].angle)), enemies[i].speed); + enemies[i].vel.x = F32_mul(F32_cos(enemies[i].angle), enemies[i].speed); + enemies[i].vel.y = F32_mul(F32_sin(enemies[i].angle), enemies[i].speed); return; } @@ -374,11 +335,9 @@ void updateBuilder(u8 i){ fix32 dx = getWrappedDelta(treasures[t].pos.x, enemies[i].pos.x); fix32 dy = treasures[t].pos.y - enemies[i].pos.y; enemies[i].speed = FIX32(1.4); - s16 angle = honeAngle( - fix32ToFix16(enemies[i].pos.x), fix32ToFix16(treasures[t].pos.x), - fix32ToFix16(enemies[i].pos.y), fix32ToFix16(treasures[t].pos.y)); - enemies[i].vel.x = fix32Mul(fix16ToFix32(cosFix16(angle)), enemies[i].speed); - enemies[i].vel.y = fix32Mul(fix16ToFix32(sinFix16(angle)), enemies[i].speed); + fix16 angle = getAngle(dx, dy); + enemies[i].vel.x = F32_mul(F32_cos(angle), enemies[i].speed); + enemies[i].vel.y = F32_mul(F32_sin(angle), enemies[i].speed); // grab check fix32 adx = dx < 0 ? -dx : dx; @@ -412,137 +371,572 @@ void updateBuilder(u8 i){ // selections and timings per phase. // // ints[0] = boss number (0-4), selects which updateBoss variant runs +// ints[1] = pattern scratch (shared across patterns, reset per phase) +// ints[2] = pattern scratch (shared across patterns, reset per phase) // ints[4] = max HP (stored at load for phase calculation) +// ints[5] = Y target clock (used by updateBoss movement) +// ints[6] = Y target position (used by updateBoss movement) +// ints[7] = pattern scratch (shared across patterns) // -// Boss 1 (L4): 25 HP, 2 phases -- radial, then aimed fan -// Boss 2 (L8): 50 HP, 3 phases -- radial, aimed fan, spiral -// Boss 3 (L12): 75 HP, 4 phases -- radial, spiral, aimed fan, double radial -// Boss 4 (L16): 100 HP, 5 phases -- adds wide spray -// Boss 5 (L20): 125 HP, 6 phases -- adds ring burst (all patterns used) +// Boss 0 (L3): 24 HP, 2 phases -- One, Two +// Boss 1 (L6): 50 HP, 3 phases -- One, Two, Three +// Boss 2 (L9): 75 HP, 4 phases -- Two, Three, Four, Five +// Boss 3 (L12): 100 HP, 5 phases -- Two, Three, Four, Five, Six +// Boss 4 (L15): 125 HP, 6 phases -- Three, Four, Five, Six, Seven, Eight // -// Available boss attack patterns: -// Radial: N bullets in even circle, random start angle. Predictable. -// Aimed Fan: N bullets in arc aimed at player. Targeted. -// Spiral: N bullets in circle, start angle rotates with clock. Mesmerizing. -// Double Radial: Two offset rings (outer fast, inner slow). Dense. -// Wide Spray: N bullets in random spread toward player. Chaotic. -// Ring Burst: N bullets in even circle (like radial but different anim). Climactic. +// 8 numbered attack patterns (bossPatternOne-Eight), easiest to hardest: +// Three: Random spray, low density +// One: Rotating 6-bullet radial bursts +// Four: Sweeping aimed double-shot +// Six: 8-bullet offset radial every 10 frames +// Two: Twin aimed spiral arms +// Five: Heavy random spray, two fire windows +// Seven: Bouncing V-shots + aimed spray +// Eight: Constant double spiral + 5-bullet radial rings // // Speed: 1 HP: varies Shoots: yes (phase-dependent) Abducts: no // ============================================================================= -// shared pattern functions -static void bossPatternRadial(u8 i, u8 count, fix32 speed){ - sfxEnemyShotB(); - s16 baseAngle = random() % 1024; - struct bulletSpawner spawner = { - .x = enemies[i].pos.x, .y = enemies[i].pos.y, - .anim = 3 + (random() % 3), .speed = speed, .angle = baseAngle, - }; - s16 step = 1024 / count; - for(u8 j = 0; j < count; j++){ - spawnBullet(spawner, EMPTY); - spawner.angle += step; + + +// static void bossPatternTwo(u8 i){ +// if(enemies[i].clock % 60 < 50 && enemies[i].clock % 10 == 0){ +// if(enemies[i].clock % 60 == 0 || enemies[i].ints[1] == 0){ +// enemies[i].ints[1] = enemyHoneAngle(i); +// enemies[i].ints[2] = 0; +// } +// struct bulletSpawner spawner = { +// .x = enemies[i].pos.x + F32_mul(F32_cos(enemies[i].ints[1] + FIX16(90)), FIX32(12)), +// .y = enemies[i].pos.y + F32_mul(F32_sin(enemies[i].ints[1] + FIX16(90)), FIX32(12)), +// .anim = 3, +// .speed = FIX32(4), +// .angle = enemies[i].ints[1] + enemies[i].ints[2] +// }; +// spawnBullet(spawner, EMPTY); +// spawner.x = enemies[i].pos.x + F32_mul(F32_cos(enemies[i].ints[1] - FIX16(90)), FIX32(12)); +// spawner.y = enemies[i].pos.y + F32_mul(F32_sin(enemies[i].ints[1] - FIX16(90)), FIX32(12)); +// spawner.angle = enemies[i].ints[1] - enemies[i].ints[2]; +// spawnBullet(spawner, EMPTY); +// enemies[i].ints[2] = angleAdd(enemies[i].ints[2], FIX16(11.25)); +// sfxEnemyShotB(); +// } +// } + +// static void bossPatternThree(u8 i){ +// if(enemies[i].clock % 60 < 40 && enemies[i].clock % 2 == 0){ +// if(enemies[i].clock % 10 == 0) sfxEnemyShotA(); +// struct bulletSpawner spawner = { +// .x = enemies[i].pos.x, +// .y = enemies[i].pos.y, +// .anim = (random() % 2) == 0 ? 0 : 1, +// .frame = 1, +// .speed = FIX32(3 + (random() % 2)), +// .angle = FIX16(random() % 360) +// }; +// spawnBullet(spawner, EMPTY); +// } +// } + +// static void bossPatternFour(u8 i){ +// if(enemies[i].clock % 60 < 40 && enemies[i].clock % 5 == 0){ +// if(enemies[i].clock % 60 == 0) +// enemies[i].ints[7] = enemyHoneAngle(i) - FIX16(45); +// if(enemies[i].clock % 10 == 0) sfxEnemyShotB(); +// struct bulletSpawner spawner = { +// .x = enemies[i].pos.x, +// .y = enemies[i].pos.y, +// .anim = 0, +// .frame = 0, +// .speed = FIX32(4 + (random() % 2)), +// .angle = enemies[i].ints[7] +// }; +// spawnBullet(spawner, EMPTY); +// spawner.speed -= FIX32(1); +// spawner.anim++; +// spawnBullet(spawner, EMPTY); +// } +// enemies[i].angle = enemyHoneAngle(i); +// enemies[i].ints[7] = angleAdd(enemies[i].ints[7], FIX16(3)); +// } + +// static void bossPatternFive(u8 i){ +// if((enemies[i].clock % 60 < 30 && enemies[i].clock % 2 == 0) || (enemies[i].clock % 60 >= 35 && enemies[i].clock % 60 < 55)){ +// if(enemies[i].clock % 10 == 0) sfxEnemyShotA(); +// struct bulletSpawner spawner = { +// .x = enemies[i].pos.x, +// .y = enemies[i].pos.y, +// .anim = (random() % 2) == 0 ? 0 : 1, +// .frame = 2, +// .speed = FIX32(4 + (random() % 2)), +// .angle = FIX16(random() % 360) +// }; +// spawnBullet(spawner, EMPTY); +// } +// } + +// static void bossPatternSix(u8 i){ +// if(enemies[i].clock % 60 < 40 && enemies[i].clock % 10 == 0){ +// sfxEnemyShotB(); +// struct bulletSpawner spawner = { +// .x = enemies[i].pos.x + F32_mul(F32_cos(enemies[i].angle), FIX32(32)), +// .y = enemies[i].pos.y + F32_mul(F32_sin(enemies[i].angle), FIX32(32)), +// .anim = enemies[i].clock % 20 == 0 ? 1 : 0, +// .frame = 1, +// .speed = FIX32(enemies[i].clock % 20 == 0 ? 3 : 4), +// .angle = FIX16(random() % 45) +// }; +// for(u8 j = 0; j < 8; j++){ +// spawnBullet(spawner, EMPTY); +// spawner.angle = angleAdd(spawner.angle, FIX16(45)); +// } +// } +// } + +// static void bossPatternSeven(u8 i){ +// if(enemies[i].clock % 60 < 30 && enemies[i].clock % 10 == 0){ +// if(enemies[i].clock % 60 == 0){ +// enemies[i].ints[7] = F32_toInt(enemies[i].pos.x); +// enemies[i].ints[2] = -16; +// } +// sfxEnemyShotA(); +// struct bulletSpawner spawner = { +// .x = enemies[i].pos.x, +// .y = enemies[i].pos.y, +// .anim = 6, +// .frame = 0, +// .speed = FIX32(7) +// }; +// void updater(u8 j){ +// if(!bullets[j].ints[5]){ +// if(bullets[j].pos.y <= 8){ +// bullets[j].pos.y = FIX32(8); +// bullets[j].vel.y *= -1; +// bullets[j].vel.x = bullets[j].vel.x >> 1; +// bullets[j].vel.y = bullets[j].vel.y >> 1; +// bullets[j].angle = bullets[j].vel.x > 0 ? FIX16(45) : FIX16(135); +// doBulletRotation(j); +// bullets[j].ints[5] = TRUE; +// } else if(bullets[j].pos.y >= FIX32(216)){ +// bullets[j].pos.y = FIX32(216); +// bullets[j].vel.y *= -1; +// bullets[j].vel.x = bullets[j].vel.x >> 1; +// bullets[j].vel.y = bullets[j].vel.y >> 1; +// bullets[j].angle = bullets[j].vel.x > 0 ? FIX16(315) : FIX16(225); +// doBulletRotation(j); +// bullets[j].ints[5] = TRUE; +// } +// } +// } +// spawner.angle = player.pos.x >= enemies[i].pos.x ? FIX16(45) : FIX16(135); +// spawnBullet(spawner, updater); +// spawner.angle = player.pos.x >= enemies[i].pos.x ? FIX16(315) : FIX16(225); +// spawnBullet(spawner, updater); +// enemies[i].ints[2] += 8; +// } else if(enemies[i].clock % 60 >= 30 && enemies[i].clock % 60 < 50 && enemies[i].clock % 2 == 0){ +// if(enemies[i].clock % 10 == 0) sfxEnemyShotB(); +// struct bulletSpawner spawner = { +// .x = enemies[i].pos.x, +// .y = enemies[i].pos.y, +// .anim = enemies[i].clock % 4 == 0 ? 0 : 1, +// .frame = 1, +// .speed = FIX32(3 + (random() % 3)), +// .angle = enemyHoneAngle(i) - (enemies[i].clock % 4 < 2 ? FIX16(31) : 0) + FIX16(random() % 31) +// }; +// spawnBullet(spawner, EMPTY); +// } +// } + +// static void bossPatternEight(u8 i){ +// if(enemies[i].clock % 8 == 0){ +// if(enemies[i].clock % 16 == 0) sfxEnemyShotA(); +// struct bulletSpawner spawner = { +// .x = enemies[i].pos.x, +// .y = enemies[i].pos.y, +// .anim = 6, +// .frame = 0, +// .angle = enemies[i].ints[1], +// .speed = FIX32(4) +// }; +// spawnBullet(spawner, EMPTY); +// spawner.angle = angleAdd(spawner.angle, FIX16(180)); +// spawnBullet(spawner, EMPTY); +// enemies[i].ints[1] = angleAdd(enemies[i].ints[1], FIX16(19.69)); +// sfxEnemyShotA(); +// } else if(enemies[i].clock % 16 == 4){ +// sfxEnemyShotB(); +// struct bulletSpawner spawner = { +// .x = enemies[i].pos.x, +// .y = enemies[i].pos.y, +// .anim = 1, +// .frame = 0, +// .angle = 0, +// .speed = FIX32(3) +// }; +// if(enemies[i].clock % 32 == 4){ +// spawner.angle = angleAdd(spawner.angle, FIX16(36)); +// spawner.anim = 0; +// } +// for(u8 j = 0; j < 5; j++){ +// spawnBullet(spawner, EMPTY); +// spawner.angle = angleAdd(spawner.angle, FIX16(72)); +// } +// } +// } + + + + +static void bossPatternOne(u8 i){ + if(enemies[i].clock % 90 < 30 && enemies[i].clock % 10 == 0){ + if(enemies[i].clock % 90 == 0){ + enemies[i].fixes[0] = FIX16(random() % 72); + enemies[i].ints[1] = F32_toInt(enemies[i].pos.x); + enemies[i].ints[2] = F32_toInt(enemies[i].pos.y); + } + struct bulletSpawner spawner = { + .x = FIX32(enemies[i].ints[1]), + .y = FIX32(enemies[i].ints[2]), + .anim = 3, + .speed = FIX32(2.5), + .angle = enemies[i].fixes[0] + }; + for(u8 j = 0; j < 5; j++){ + spawnBullet(spawner, EMPTY); + spawner.angle += FIX16(72); + } + sfxEnemyShotA(); + } else if(enemies[i].clock % 90 == 40 || enemies[i].clock % 90 == 50){ + if(enemies[i].clock % 90 == 40) + enemies[i].ints[1] = random() % 60; + struct bulletSpawner spawner = { + .x = enemies[i].pos.x, + .y = enemies[i].pos.y, + .anim = 1, + .frame = 1, + .speed = FIX32(enemies[i].clock % 90 == 40 ? 3 : 4), + .angle = FIX16(enemies[i].ints[1]) + }; + + for(u8 j = 0; j < 6; j++){ + spawnBullet(spawner, EMPTY); + spawner.angle += FIX16(60); + } + sfxEnemyShotB(); } } -static void bossPatternAimedFan(u8 i, u8 count, s16 spread, fix32 speed){ - sfxEnemyShotA(); - fix32 dx = getWrappedDelta(player.pos.x, enemies[i].pos.x); - fix32 dy = player.pos.y - enemies[i].pos.y; - s16 aimAngle = honeAngle( - fix32ToFix16(enemies[i].pos.x), fix32ToFix16(enemies[i].pos.x) + fix32ToFix16(dx), - fix32ToFix16(enemies[i].pos.y), fix32ToFix16(enemies[i].pos.y) + fix32ToFix16(dy)); - if(player.respawnClock > 0) aimAngle = (aimAngle + 512) % 1024; - s16 step = (count > 1) ? (spread * 2) / (count - 1) : 0; - struct bulletSpawner spawner = { - .x = enemies[i].pos.x, .y = enemies[i].pos.y, - .anim = 9 + (random() % 3), .speed = speed, .angle = aimAngle - spread, - }; - for(u8 j = 0; j < count; j++){ - spawnBullet(spawner, EMPTY); - spawner.angle += step; +static void bossPatternTwo(u8 i){ + if(enemies[i].clock % 30 == 0){ + struct bulletSpawner spawner = { + .x = enemies[i].pos.x, + .y = enemies[i].pos.y, + .anim = 1, + .angle = enemyHoneAngle(i), + .speed = FIX32(3) + }; + spawner.ints[0] = random() % 72; + spawner.ints[1] = enemies[i].clock % 60 == 0 ? 0 : 1; + void updater(u8 j){ + if(bullets[j].clock > 0 && bullets[j].clock % 8 == 0){ + struct bulletSpawner spawner = { + .x = bullets[j].pos.x, + .y = bullets[j].pos.y, + .anim = 4, + .angle = FIX16(bullets[j].ints[0]), + .speed = FIX32(4) + }; + spawnBullet(spawner, EMPTY); + if(bullets[j].ints[1] == 1){ + bullets[j].ints[0] += 72; + if(bullets[j].ints[0] >= 360) + bullets[j].ints[0] -= 360; + } else { + bullets[j].ints[0] -= 72; + if(bullets[j].ints[0] <= 0) + bullets[j].ints[0] += 360; + } + } + if(bullets[j].clock == 60) killBullet(j, TRUE); + } + spawnBullet(spawner, updater); } } -static void bossPatternSpiral(u8 i, u8 count, fix32 speed){ - sfxEnemyShotC(); - s16 baseAngle = (enemies[i].clock * 17) % 1024; - struct bulletSpawner spawner = { - .x = enemies[i].pos.x, .y = enemies[i].pos.y, - .anim = 6 + (random() % 3), .speed = speed, .angle = baseAngle, - }; - s16 step = 1024 / count; - for(u8 j = 0; j < count; j++){ - spawnBullet(spawner, EMPTY); - spawner.angle += step; +static void bossPatternThree(u8 i){ + if(enemies[i].clock % 10 == 0 && enemies[i].clock % 60 < 50){ + if(enemies[i].clock % 60 == 0){ + enemies[i].fixes[0] = 0; + } + struct bulletSpawner spawner = { + .x = enemies[i].pos.x, + .y = enemies[i].pos.y, + .angle = enemies[i].clock % 120 < 60 ? enemies[i].fixes[0] : FIX16(random() % 360), + .speed = FIX32(4) + }; + for(u8 j = 0; j < 4; j++){ + if(enemies[i].clock % 120 < 60){ + spawner.anim = j % 2 == 0 ? 3 : 4; + } else { + spawner.anim = j % 2 == 0 ? 9 : 10; + } + spawnBullet(spawner, EMPTY); + spawner.angle += FIX16(90); + } + if(enemies[i].clock % 120 < 60){ + if(enemies[i].clock % 240 < 120){ + enemies[i].fixes[0] += FIX16(10); + } else { + enemies[i].fixes[0] -= FIX16(10); + } + } + } else if(enemies[i].clock % 30 == 15){ + struct bulletSpawner spawner = { + .x = enemies[i].pos.x, + .y = enemies[i].pos.y, + .anim = 1, + .angle = FIX16(random() % 45), + .speed = FIX32(3) + }; + for(u8 j = 0; j < 8; j++){ + spawner.frame = j % 2 == 0 ? 0 : 1; + spawnBullet(spawner, EMPTY); + spawner.angle += FIX16(45); + } } } -static void bossPatternDoubleRadial(u8 i, u8 count, fix32 speed){ - sfxEnemyShotB(); - s16 baseAngle = random() % 1024; - s16 step = 1024 / count; - struct bulletSpawner spawner = { - .x = enemies[i].pos.x, .y = enemies[i].pos.y, - .anim = 3 + (random() % 3), .speed = speed, .angle = baseAngle, - }; - for(u8 j = 0; j < count; j++){ - spawnBullet(spawner, EMPTY); - spawner.angle += step; - } - spawner.angle = baseAngle + step / 2; - spawner.speed = speed - FIX32(1); - spawner.anim = 6 + (random() % 3); - for(u8 j = 0; j < count; j++){ - spawnBullet(spawner, EMPTY); - spawner.angle += step; +static void bossPatternFour(u8 i){ + if(enemies[i].clock % 60 == 0){ + struct bulletSpawner spawner = { + .x = enemies[i].pos.x, + .y = enemies[i].pos.y, + .angle = FIX16(random() % 45), + .speed = FIX32(5.5) + }; + void updater(u8 j){ + if(bullets[j].clock >= 5 && bullets[j].clock % 5 == 0 && bullets[j].clock < 15){ + bullets[j].vel.x = F32_mul(bullets[j].vel.x, FIX32(0.67)); + bullets[j].vel.y = F32_mul(bullets[j].vel.y, FIX32(0.67)); + } + } + for(u8 j = 0; j < 8; j++){ + spawner.anim = j % 2 == 0 ? 6 : 7; + spawnBullet(spawner, updater); + spawner.angle += FIX16(45); + } + } else if(enemies[i].clock % 60 >= 10 && enemies[i].clock % 60 < 50 && enemies[i].clock % 5 == 0){ + struct bulletSpawner spawner = { + .x = enemies[i].pos.x, + .y = enemies[i].pos.y, + .angle = enemyHoneAngle(i) - FIX16(22.5) + FIX16(random() % 45) - FIX16(180), + .speed = FIX32(4), + .anim = enemies[i].clock % 20 == 0 ? 3 : 4 + }; + void updater(u8 j){ + if(bullets[j].clock >= 4 && bullets[j].clock % 4 == 0 && bullets[j].clock < 32){ + bullets[j].speed -= FIX32(1.5); + bullets[j].vel.x = F32_mul(F32_cos(bullets[j].angle), bullets[j].speed); + bullets[j].vel.y = F32_mul(F32_sin(bullets[j].angle), bullets[j].speed); + } + } + if(spawner.angle <= 0) spawner.angle += FIX16(360); + spawnBullet(spawner, updater); } } -static void bossPatternWideSpray(u8 i, u8 count, fix32 speed){ - sfxEnemyShotA(); - fix32 dx = getWrappedDelta(player.pos.x, enemies[i].pos.x); - fix32 dy = player.pos.y - enemies[i].pos.y; - s16 aimAngle = honeAngle( - fix32ToFix16(enemies[i].pos.x), fix32ToFix16(enemies[i].pos.x) + fix32ToFix16(dx), - fix32ToFix16(enemies[i].pos.y), fix32ToFix16(enemies[i].pos.y) + fix32ToFix16(dy)); - if(player.respawnClock > 0) aimAngle = (aimAngle + 512) % 1024; - struct bulletSpawner spawner = { - .x = enemies[i].pos.x, .y = enemies[i].pos.y, - .anim = 3 + (random() % 9), .speed = speed, .angle = aimAngle, - }; - for(u8 j = 0; j < count; j++){ - spawner.angle = aimAngle - 128 + (random() % 256); - spawner.speed = speed - FIX32(random() % 2); - spawnBullet(spawner, EMPTY); +static void bossPatternFive(u8 i){ + if(enemies[i].clock % 60 < 30 && enemies[i].clock % 10 == 0){ + struct bulletSpawner spawner = { + .x = enemies[i].pos.x, + .y = enemies[i].pos.y, + .anim = enemies[i].clock % 30 == 0 ? 7 : 6, + .angle = FIX16(30) + FIX16(random() % 10), + .speed = FIX32(4) + }; + spawner.ints[1] = random() % 2; + void updater(u8 j){ + if(bullets[j].ints[0] == 0 && (bullets[j].pos.y <= FIX32(8) || bullets[j].pos.y >= (GAME_H_F - FIX32(8)))){ + bullets[j].ints[0] = 1; + bullets[j].angle = F16_normalizeAngle(FIX16(360) - bullets[j].angle); + bullets[j].vel.x = F32_mul(F32_cos(bullets[j].angle), bullets[j].speed); + bullets[j].vel.y = F32_mul(F32_sin(bullets[j].angle), bullets[j].speed); + doBulletRotation(j); + } else if(bullets[j].ints[0] == 1 && bullets[j].clock % 8 == 0){ + bullets[j].angle += FIX16(bullets[j].ints[1] == 0 ? 2 : -2); + bullets[j].vel.x = F32_mul(F32_cos(bullets[j].angle), bullets[j].speed); + bullets[j].vel.y = F32_mul(F32_sin(bullets[j].angle), bullets[j].speed); + doBulletRotation(j); + } + } + for(u8 j = 0; j < 6; j++){ + spawnBullet(spawner, updater); + spawner.angle += FIX16(j == 2 ? 90 : 45); + } + } else if(enemies[i].clock % 60 == 40){ + struct bulletSpawner spawner = { + .x = enemies[i].pos.x, + .y = enemies[i].pos.y, + .anim = 1, + .angle = FIX16(random() % 45), + .speed = FIX32(3) + }; + for(u8 j = 0; j < 8; j++){ + spawnBullet(spawner, EMPTY); + spawner.angle += FIX16(45); + } } } -static void bossPatternRingBurst(u8 i, u8 count, fix32 speed){ - sfxEnemyShotB(); - sfxEnemyShotC(); - s16 baseAngle = random() % 1024; - s16 step = 1024 / count; - struct bulletSpawner spawner = { - .x = enemies[i].pos.x, .y = enemies[i].pos.y, - .anim = 9 + (random() % 3), .speed = speed, .angle = baseAngle, - }; - for(u8 j = 0; j < count; j++){ - spawnBullet(spawner, EMPTY); - spawner.angle += step; +static void bossPatternSix(u8 i){ + if(enemies[i].clock % 60 == 0){ + struct bulletSpawner spawner = { + .x = enemies[i].pos.x, + .y = enemies[i].pos.y, + .anim = 2, + .frame = 0, + .angle = FIX16(random() % 45), + .speed = FIX32(3) + }; + void updater(u8 j){ + if(bullets[j].clock % 10 == 0 && bullets[j].clock > 0){ + bullets[j].angle = F16_normalizeAngle(bullets[j].angle + FIX16(bullets[j].ints[0] == 0 ? 10 : -10)); + updateBulletVel(j); + } + } + for(u8 j = 0; j < 8; j++){ + spawner.ints[0] = 0; + spawnBullet(spawner, updater); + spawner.ints[0] = 1; + spawnBullet(spawner, updater); + spawner.angle += FIX16(45); + } + } else if(enemies[i].clock % 60 == 20 || enemies[i].clock % 60 == 30 || enemies[i].clock % 60 == 40){ + if(enemies[i].clock % 60 == 20){ + enemies[i].fixes[0] = random() % FIX16(22.5); + } + struct bulletSpawner spawner = { + .x = enemies[i].pos.x, + .y = enemies[i].pos.y, + .anim = enemies[i].clock % 60 == 30 ? 1 : 0, + .frame = 1, + .angle = enemies[i].fixes[0] + FIX16(enemies[i].clock % 60 == 30 ? 22.5 : 0), + .speed = FIX32(4) + }; + for(u8 j = 0; j < 8; j++){ + spawnBullet(spawner, EMPTY); + spawner.angle += FIX16(45); + } } } -// get current phase based on HP. phase 0 = full health, higher = more damaged -static u8 getBossPhase(u8 i, u8 numPhases){ - s16 maxHp = enemies[i].ints[4]; - if(maxHp <= 0) return 0; - s16 lost = maxHp - enemies[i].hp; - if(lost < 0) lost = 0; - u8 phase = (lost * numPhases) / maxHp; - if(phase >= numPhases) phase = numPhases - 1; - return phase; +static void bossPatternSeven(u8 i){ + if(enemies[i].clock % 3 == 0){ + struct bulletSpawner spawner = { + .x = enemies[i].pos.x, + .y = enemies[i].pos.y, + .anim = 0, + .frame = 0, + .angle = FIX16(random() % 360), + .speed = FIX32(4) + }; + if(enemies[i].clock % 6 == 0) + spawner.frame = 1; + void updater(u8 j){ + if(bullets[j].ints[0] == 0 && (bullets[j].pos.y <= FIX32(8) || bullets[j].pos.y >= (GAME_H_F - FIX32(8)))){ + bullets[j].ints[0] = TRUE; + bullets[j].anim = 1; + bullets[j].vel.y = F32_mul(bullets[j].vel.y, FIX32(-0.5)); + SPR_setAnim(bullets[j].image, bullets[j].anim); + SPR_setFrame(bullets[j].image, bullets[j].frame); + } + } + spawnBullet(spawner, updater); + } +} + +static void bossPatternEight(u8 i){ + if(enemies[i].clock % 60 >= 30 && enemies[i].clock % 60 < 34){ + if(enemies[i].clock % 60 == 30){ + enemies[i].ints[1] = F32_toInt(enemies[i].pos.x); + enemies[i].ints[2] = F32_toInt(enemies[i].pos.y); + enemies[i].fixes[0] = enemyHoneAngle(i) - FIX16(30); + enemies[i].fixes[2] = FIX16(0); + } + struct bulletSpawner spawner = { + .x = FIX32(enemies[i].ints[1]), + .y = FIX32(enemies[i].ints[2]), + .anim = 1, + .frame = 0, + .angle = enemies[i].fixes[0], + .speed = FIX32(2.5) + F16_toFix32(enemies[i].fixes[2]) + }; + for(u8 j = 0; j < 3; j++){ + spawnBullet(spawner, EMPTY); + spawner.angle += FIX16(30); + } + enemies[i].fixes[2] += FIX16(0.5); + } else if(enemies[i].clock % 60 == 0){ + struct bulletSpawner spawner = { + .x = enemies[i].pos.x, + .y = enemies[i].pos.y, + .anim = 2, + .frame = 1, + .angle = FIX16(random() % 45), + .speed = FIX32(3) + }; + for(u8 j = 0; j < 8; j++){ + spawnBullet(spawner, EMPTY); + spawner.angle += FIX16(45); + } + } +} + +static void updateBossOne(u8 i){ + bossPatternEight(i); + // u8 phase = getBossPhase(i, 2); + // if(phase == 0) bossPatternOne(i); + // else bossPatternTwo(i); +} + + + + +// Boss 1 (L6): 3 phases, 50 HP +static void updateBossTwo(u8 i){ + u8 phase = getBossPhase(i, 3); + if(phase == 0) bossPatternOne(i); + else if(phase == 1) bossPatternTwo(i); + else bossPatternThree(i); +} + +// Boss 2 (L9): 4 phases, 75 HP +static void updateBossThree(u8 i){ + u8 phase = getBossPhase(i, 4); + if(phase == 0) bossPatternTwo(i); + else if(phase == 1) bossPatternThree(i); + else if(phase == 2) bossPatternFour(i); + else bossPatternFive(i); +} + +// Boss 3 (L12): 5 phases, 100 HP +static void updateBossFour(u8 i){ + u8 phase = getBossPhase(i, 5); + if(phase == 0) bossPatternTwo(i); + else if(phase == 1) bossPatternThree(i); + else if(phase == 2) bossPatternFour(i); + else if(phase == 3) bossPatternFive(i); + else bossPatternSix(i); +} + +// Boss 4 (L15): 6 phases, 125 HP +static void updateBossFive(u8 i){ + u8 phase = getBossPhase(i, 6); + if(phase == 0) bossPatternThree(i); + else if(phase == 1) bossPatternFour(i); + else if(phase == 2) bossPatternFive(i); + else if(phase == 3) bossPatternSix(i); + else if(phase == 4) bossPatternSeven(i); + else bossPatternEight(i); } void loadBoss(u8 i){ @@ -552,81 +946,65 @@ void loadBoss(u8 i){ pendingBossNum = 0; enemies[i].ints[1] = 0; enemies[i].ints[4] = enemies[i].hp; - enemies[i].angle = random() % 1024; - enemies[i].speed = FIX32(1); -} - -// Boss 1 (L6): 2 patterns, 25 HP -static void updateBossOne(u8 i){ - u8 phase = getBossPhase(i, 2); - if(phase == 0){ - if(enemies[i].clock % 50 == 0) bossPatternRadial(i, 10, FIX32(3)); - } else { - if(enemies[i].clock % 40 == 0) bossPatternAimedFan(i, 6, 80, FIX32(3)); - } -} - -// Boss 2 (L12): 3 patterns, 50 HP -static void updateBossTwo(u8 i){ - u8 phase = getBossPhase(i, 3); - if(phase == 0){ - if(enemies[i].clock % 50 == 0) bossPatternRadial(i, 12, FIX32(3)); - } else if(phase == 1){ - if(enemies[i].clock % 40 == 0) bossPatternAimedFan(i, 8, 96, FIX32(3)); - } else { - if(enemies[i].clock % 35 == 0) bossPatternSpiral(i, 6, FIX32(4)); - } -} - -// Boss 3 (L18): 4 patterns, 75 HP -static void updateBossThree(u8 i){ - u8 phase = getBossPhase(i, 4); - if(phase == 0){ - if(enemies[i].clock % 50 == 0) bossPatternRadial(i, 12, FIX32(3)); - } else if(phase == 1){ - if(enemies[i].clock % 40 == 0) bossPatternSpiral(i, 8, FIX32(3)); - } else if(phase == 2){ - if(enemies[i].clock % 35 == 0) bossPatternAimedFan(i, 10, 112, FIX32(4)); - } else { - if(enemies[i].clock % 30 == 0) bossPatternDoubleRadial(i, 8, FIX32(4)); - } -} - -// Boss 4 (L24): 5 patterns, 100 HP -static void updateBossFour(u8 i){ - u8 phase = getBossPhase(i, 5); - if(phase == 0){ - if(enemies[i].clock % 50 == 0) bossPatternRadial(i, 14, FIX32(3)); - } else if(phase == 1){ - if(enemies[i].clock % 40 == 0) bossPatternAimedFan(i, 8, 96, FIX32(3)); - } else if(phase == 2){ - if(enemies[i].clock % 35 == 0) bossPatternSpiral(i, 8, FIX32(4)); - } else if(phase == 3){ - if(enemies[i].clock % 30 == 0) bossPatternDoubleRadial(i, 10, FIX32(4)); - } else { - if(enemies[i].clock % 25 == 0) bossPatternWideSpray(i, 12, FIX32(4)); - } -} - -// Boss 5 (L30): 6 patterns, 125 HP -static void updateBossFive(u8 i){ - u8 phase = getBossPhase(i, 6); - if(phase == 0){ - if(enemies[i].clock % 50 == 0) bossPatternRadial(i, 16, FIX32(3)); - } else if(phase == 1){ - if(enemies[i].clock % 40 == 0) bossPatternAimedFan(i, 10, 112, FIX32(3)); - } else if(phase == 2){ - if(enemies[i].clock % 35 == 0) bossPatternSpiral(i, 10, FIX32(4)); - } else if(phase == 3){ - if(enemies[i].clock % 30 == 0) bossPatternDoubleRadial(i, 10, FIX32(4)); - } else if(phase == 4){ - if(enemies[i].clock % 25 == 0) bossPatternWideSpray(i, 14, FIX32(5)); - } else { - if(enemies[i].clock % 20 == 0) bossPatternRingBurst(i, 16, FIX32(5)); - } + enemies[i].ints[5] = random() % 90; // Y target clock (start random offset) + enemies[i].ints[6] = 72 + (random() % 80); + enemies[i].angle = FIX16(random() % 360); + enemies[i].speed = FIX32(4); } void updateBoss(u8 i){ + // Homing: recalculate heading and speed toward player every 30 frames (X-axis only) + if(enemies[i].clock % 30 == 0){ + fix32 dx = getWrappedDelta(player.pos.x, enemies[i].pos.x); + enemies[i].angle = getAngle(dx, FIX32(112) - enemies[i].pos.y); + if(player.respawnClock > 0) enemies[i].angle = F16_normalizeAngle(enemies[i].angle + FIX16(180)); + enemies[i].speed = (dx > FIX32(-128) && dx < FIX32(128)) ? FIX32(0.5) : FIX32(3); + enemies[i].vel.x = F32_mul(F32_cos(enemies[i].angle), enemies[i].speed); + } + + // Y movement: pick random target every 90 frames, move toward it with 10px dead zone + enemies[i].ints[5]++; + if(enemies[i].ints[5] >= 90){ + enemies[i].ints[5] = 0; + enemies[i].ints[6] = 72 + (random() % 80); // new Y target in [72, 151] + } + fix32 dy = FIX32(enemies[i].ints[6]) - enemies[i].pos.y; + if(dy > FIX32(10)){ + enemies[i].vel.y = FIX32(1); // move down + } else if(dy < FIX32(-10)){ + enemies[i].vel.y = FIX32(-1); // move up + } else { + enemies[i].vel.y = 0; // in dead zone + } + + // Guardrails: reflect at Y bounds 72 and 152 + if(enemies[i].pos.y < FIX32(72)){ + enemies[i].pos.y = FIX32(72); + enemies[i].vel.y = FIX32(1); // reflect down + enemies[i].ints[6] = 80; // pick new target below + } + if(enemies[i].pos.y > FIX32(152)){ + enemies[i].pos.y = FIX32(152); + enemies[i].vel.y = FIX32(-1); // reflect up + enemies[i].ints[6] = 144; // pick new target above + } + // Directional sprite: 4 orientations (right, down, left, up) with 2 frames each + fix16 a = F16_normalizeAngle(enemies[i].angle); + if(a < FIX16(45) || a >= FIX16(315)){ + SPR_setAnim(enemies[i].image, 1); + SPR_setHFlip(enemies[i].image, FALSE); + } else if(a < FIX16(135)){ + SPR_setAnim(enemies[i].image, 0); + SPR_setHFlip(enemies[i].image, FALSE); + } else if(a < FIX16(225)){ + SPR_setAnim(enemies[i].image, 1); + SPR_setHFlip(enemies[i].image, TRUE); + } else { + SPR_setAnim(enemies[i].image, 2); + SPR_setHFlip(enemies[i].image, FALSE); + } + SPR_setFrame(enemies[i].image, (enemies[i].clock / 30) % 2); + if(!enemies[i].onScreen) return; switch(enemies[i].ints[0]){ case 0: updateBossOne(i); break; diff --git a/src/global.h b/src/global.h index 4ae51bb..005695e 100644 --- a/src/global.h +++ b/src/global.h @@ -5,6 +5,7 @@ void sfxEnemyShotB(); void sfxEnemyShotC(); void sfxExplosion(); void sfxPickup(); +void sfxGraze(); void loadMap(); void loadGame(); @@ -21,12 +22,34 @@ u32 clock; #define GAME_WRAP (SECTION_SIZE * SECTION_COUNT) #define CULL_LIMIT FIX32(240) +#define SCREEN_LIMIT FIX32(208) // max player-to-screen-edge distance (320 - CAMERA_X) -#define MUSIC_VOLUME 50 -// #define MUSIC_VOLUME 0 +// #define MUSIC_VOLUME 50 +#define MUSIC_VOLUME 0 u32 score; +u32 highScore; +u32 tempHighScore; +u32 grazeCount; +u32 nextExtendScore; +#define EXTEND_SCORE 25000 #define SCORE_LENGTH 8 +#define GRAZE_RADIUS 16 + +#define SCORE_SRAM 0x0033 + +void getHighScore(){ + SRAM_enable(); + tempHighScore = SRAM_readLong(SCORE_SRAM); + if(tempHighScore > 0 && tempHighScore < 1000000) highScore = tempHighScore; + SRAM_disable(); +} + +static void saveHighScore(){ + SRAM_enable(); + SRAM_writeLong(SCORE_SRAM, highScore); + SRAM_disable(); +} #define FIRST_ROTATING_BULLET 3 @@ -34,12 +57,20 @@ u32 score; #define MAP_Y 1 #define MAP_W 38 #define MAP_H 3 +#define MAP_SCALE (F32_toInt(GAME_WRAP) / MAP_W) void EMPTY(s16 i){(void)i;} bool started; bool gameOver; bool paused, isPausing; +bool isAttract; +u16 attractClock; +#define ATTRACT_LIMIT 900 // frames of title idle before attract triggers +#define ATTRACT_DURATION 1800 // 30 seconds of demo gameplay +#define ATTRACT_LEVEL 1 // level index for attract mode (L12: Boss 4, 3 gunners) +#define START_LEVEL 2 // offset added to starting level (0 = normal start) +// #define START_LEVEL 0 // offset added to starting level (0 = normal start) s16 enemyCount, bulletCount; u8 level; s16 pendingBossHp; @@ -59,6 +90,7 @@ struct controls { bool left, right, up, down, a, b, c, start; }; struct controls ctrl; +struct controls ctrlHW; // hardware-only copy — never overridden by AI void updateControls(u16 joy, u16 changed, u16 state){ (void)changed; // Unused parameter if(joy == JOY_1){ @@ -70,6 +102,7 @@ void updateControls(u16 joy, u16 changed, u16 state){ ctrl.b = (state & BUTTON_B); ctrl.c = (state & BUTTON_C); ctrl.start = (state & BUTTON_START); + ctrlHW = ctrl; } } @@ -79,10 +112,13 @@ struct playerStruct { Vect2D_f32 pos, vel; s16 shotAngle; u8 lives, recoveringClock, respawnClock; + bool recoverFlash; // TRUE only after death, not on level-start grace + bool pendingShow; // show sprite after next position update (avoids 1-frame position flicker) fix32 camera; Sprite* image; }; struct playerStruct player; +fix32 playerScrollVelY; // player.vel.y zeroed when clamped at top/bottom bound bool killBullets; @@ -93,14 +129,17 @@ struct bulletSpawner { fix32 x, y, speed; Vect2D_f32 vel; s16 angle, anim, frame; + s16 ints[PROP_COUNT]; bool top, player; }; struct bullet { - bool active, player, vFlip, hFlip, explosion; + fix32 speed; + bool active, player, vFlip, hFlip, explosion, grazed; Vect2D_f32 pos, vel; Sprite* image; s16 clock, angle, anim, frame; s16 dist; + s16 ints[PROP_COUNT]; void (*updater)(s16); }; struct bullet bullets[BULLET_COUNT]; @@ -119,13 +158,14 @@ struct bullet bullets[BULLET_COUNT]; struct enemy { bool active, onScreen; u8 type; - s16 hp; + s16 hp, frame, anim; s16 angle, off; u32 clock; fix32 speed; Vect2D_f32 vel, pos; Sprite* image; s16 ints[PROP_COUNT]; + fix16 fixes[PROP_COUNT]; }; struct enemy enemies[ENEMY_COUNT]; @@ -148,6 +188,9 @@ struct treasure { struct treasure treasures[TREASURE_COUNT]; bool treasureBeingCarried; s16 collectedCount; +u16 levelEnemiesKilled; +u16 statEnemiesKilled; +s16 statTreasures; void killTreasure(u8 i){ if(treasures[i].state == TREASURE_CARRIED && treasures[i].carriedBy >= 0){ @@ -160,19 +203,17 @@ void killTreasure(u8 i){ void killBullet(u8 i, bool explode){ if(explode){ + s16 a = bullets[i].anim; + s16 explosionAnim; if(bullets[i].player){ - SPR_setAnim(bullets[i].image, 1); + explosionAnim = 16; + } else if(a < FIRST_ROTATING_BULLET){ + explosionAnim = 13 + bullets[i].frame; } else { - s16 a = bullets[i].anim; - s16 explosionAnim; - if(a < FIRST_ROTATING_BULLET){ - explosionAnim = 13 + bullets[i].frame; - } else { - s16 mod = a % 3; - explosionAnim = 13 + mod; - } - SPR_setAnim(bullets[i].image, explosionAnim); + s16 mod = a % 3; + explosionAnim = 13 + mod; } + SPR_setAnim(bullets[i].image, explosionAnim); bullets[i].clock = 0; bullets[i].frame = 0; bullets[i].explosion = TRUE; @@ -186,6 +227,7 @@ void killBullet(u8 i, bool explode){ } void killEnemy(u8 i){ + if(isAttract) return; enemies[i].hp--; if(enemies[i].hp > 0) return; if(enemies[i].ints[3] >= 0){ @@ -200,6 +242,7 @@ void killEnemy(u8 i){ } enemies[i].active = FALSE; SPR_releaseSprite(enemies[i].image); + levelEnemiesKilled++; } static fix32 getWrappedDelta(fix32 a, fix32 b) { @@ -214,37 +257,61 @@ static fix32 getWrappedDelta(fix32 a, fix32 b) { static s16 getScreenX(fix32 worldX, fix32 camera) { fix32 screenX = worldX - camera; - if (screenX < FIX32(-256)) { + if (screenX < -(GAME_WRAP / 2)) { screenX += GAME_WRAP; - } else if (screenX > FIX32(256)) { + } else if (screenX > (GAME_WRAP / 2)) { screenX -= GAME_WRAP; } - return fix32ToInt(screenX); + return F32_toInt(screenX); } -// homing -#define PI_MOD 2.84444444444 -#define PI_F FIX16(3.14159265358 * PI_MOD) -#define PI_F_2 FIX16(1.57079632679 * PI_MOD) -#define PI_F_4 FIX16(0.78539816339 * PI_MOD) -fix16 arctan(fix16 x) { - return fix16Mul(PI_F_4, x) - fix16Mul(fix16Mul(x, (abs(x) - 1)), (FIX16(0.245) + fix16Mul(FIX16(0.066), abs(x)))); +// homing -- degree-based using SGDK F16_atan2 (returns fix16 degrees) +static fix16 getAngle(fix32 dx, fix32 dy){ + s16 ix = (s16)(F32_toInt(dx) >> 2); + s16 iy = (s16)(F32_toInt(dy) >> 2); + if(ix == 0 && iy == 0) return 0; + return F16_normalizeAngle(F16_atan2(FIX16(iy), FIX16(ix))); } -fix16 arctan2(fix16 y, fix16 x) { - return x >= 0 ? - (y >= 0 ? (y < x ? arctan(fix16Div(y, x)) : PI_F_2 - arctan(fix16Div(x, y))) : (-y < x ? arctan(fix16Div(y, x)) : -PI_F_2 - arctan(fix16Div(x, y)))) : - (y >= 0 ? (y < -x ? arctan(fix16Div(y, x)) + PI_F : PI_F_2 - arctan(fix16Div(x, y))) : (-y < -x ? arctan(fix16Div(y, x)) - PI_F : -PI_F_2 - arctan(fix16Div(x, y)))); + +// safe angle accumulation -- keeps angle in [-180, 180) so adding up to 180° can't overflow s16 +static s16 angleAdd(s16 a, s16 step){ + if(a >= FIX16(180)) a -= FIX16(360); + return a + step; } -s16 arcAngle; -s16 honeAngle(fix16 x1, fix16 x2, fix16 y1, fix16 y2){ - arcAngle = arctan2(y2 - y1, x2 - x1); - if(arcAngle >= 128) arcAngle -= 32; - if(arcAngle >= 384) arcAngle -= 32; - if(arcAngle < 0){ - arcAngle = 1024 + arcAngle; - if(arcAngle < 896) arcAngle += 32; - if(arcAngle < 640) arcAngle += 32; + +static bool isBossLevel(u8 lvl){ + return (lvl >= 2) && ((lvl + 1) % 3 == 0); +} + +#define FONT_THEME_RED 0 +#define FONT_THEME_GREEN 1 +#define FONT_THEME_BLUE 2 + +u16 fontPal[16]; +void loadFontPalette(u8 theme) { + u16 coloredPalette[16]; + u8 i; + for(i = 0; i < 16; i++) { + u16 color = font.palette->data[i]; + u16 r = color & 0xF; + u16 g = (color >> 4) & 0xF; + u16 b = (color >> 8) & 0xF; + switch(theme) { + case FONT_THEME_GREEN: + coloredPalette[i] = (b << 8) | (r << 4) | g; + break; + case FONT_THEME_BLUE: { + u16 newB = r > b ? r : b; + coloredPalette[i] = (newB << 8) | (g << 4) | (r >> 1); + break; + } + default: // FONT_THEME_RED + coloredPalette[i] = color; + break; + } } - return arcAngle; + memcpy(fontPal, coloredPalette, 16 * sizeof(u16)); + PAL_setPalette(PAL3, coloredPalette, DMA_QUEUE); + VDP_setTextPalette(PAL3); } \ No newline at end of file diff --git a/src/main.c b/src/main.c index 88c67e8..d266886 100644 --- a/src/main.c +++ b/src/main.c @@ -10,19 +10,32 @@ #include "stage.h" #include "chrome.h" #include "start.h" +#include "starfield.h" #include "sfx.h" static void loadInternals(){ JOY_init(); JOY_setEventHandler(&updateControls); - SPR_init(); VDP_setPlaneSize(128, 32, TRUE); + SPR_init(); VDP_loadFont(font.tileset, DMA); PAL_setPalette(PAL0, font.palette->data, DMA); PAL_setPalette(PAL1, shadow.palette->data, CPU); + PAL_setPalette(PAL2, shadow.palette->data, CPU); VDP_setTextPriority(1); } +static bool attractEnding; + +static void startLevelFadeIn(){ + u16 ft[48]; + memcpy(ft, font.palette->data, 16 * sizeof(u16)); + memcpy(ft + 16, shadow.palette->data, 16 * sizeof(u16)); + memcpy(ft + 32, bgPal, 16 * sizeof(u16)); + PAL_setColors(0, palette_black, 48, CPU); + PAL_fadeIn(0, 47, ft, 20, TRUE); +} + void clearLevel(){ for(s16 i = 0; i < BULLET_COUNT; i++) if(bullets[i].active) killBullet(i, FALSE); @@ -34,6 +47,7 @@ void clearLevel(){ collectedCount = 0; allTreasureCollected = FALSE; treasureCollectedClock = 0; + levelEnemiesKilled = 0; // black out everything SPR_setVisibility(player.image, HIDDEN); VDP_clearTileMapRect(BG_A, 0, 0, 128, 32); @@ -41,43 +55,90 @@ void clearLevel(){ } void loadGame(){ + VDP_setVerticalScroll(BG_A, 0); + score = 0; + nextExtendScore = EXTEND_SCORE; loadBackground(); loadPlayer(); loadChrome(); - loadLevel(0); - XGM2_play(stageMusic); - XGM2_setFMVolume(MUSIC_VOLUME); - XGM2_setPSGVolume(MUSIC_VOLUME); + loadLevel(isAttract ? ATTRACT_LEVEL : START_LEVEL); +#if MUSIC_VOLUME > 0 + if(!isAttract) XGM2_play(isBossLevel(level) ? bossMusic : stageMusic); +#endif player.recoveringClock = 240; + player.recoverFlash = FALSE; killBullets = TRUE; + attractEnding = FALSE; started = TRUE; + startLevelFadeIn(); } static void updateGame(){ + if(isAttract){ + if(!attractEnding){ + if(ctrlHW.start || ctrlHW.a || ctrlHW.b || ctrlHW.c){ + attractEnding = TRUE; + PAL_fadeOut(0, 47, 20, TRUE); + } + if(attractClock > 0){ + attractClock--; + if(attractClock == 20) + PAL_fadeOut(0, 47, 20, TRUE); + } else { + SYS_hardReset(); + } + } else if(!PAL_isDoingFade()){ + SYS_hardReset(); + } + } updateChrome(); - updateSfx(); if(levelClearing){ levelClearClock++; + if(levelClearClock == 73) + XGM2_stop(); if(levelClearClock == 1){ clearLevel(); + loadStarfield(level % 3); + u16 transPal[32]; + memcpy(transPal, font.palette->data, 16 * sizeof(u16)); + memcpy(transPal + 16, shadow.palette->data, 16 * sizeof(u16)); + PAL_fadeIn(0, 31, transPal, 20, TRUE); } - if(levelClearClock >= 120){ + if(levelClearClock == 220) + PAL_fadeOut(0, 31, 20, TRUE); + if(levelClearClock >= 240){ levelClearing = FALSE; player.pos.y = FIX32(112); player.camera = player.pos.x - FIX32(160); playerVelX = 0; + clearStarfield(); loadBackground(); loadChrome(); loadLevel(level + 1); - SPR_setVisibility(player.image, VISIBLE); + startLevelFadeIn(); + player.pendingShow = TRUE; player.recoveringClock = 240; + player.recoverFlash = FALSE; killBullets = TRUE; + XGM2_stop(); +#if MUSIC_VOLUME > 0 + if(!isAttract) XGM2_play(isBossLevel(level) ? bossMusic : stageMusic); +#endif } + if(levelClearing) updateStarfield(); return; } if(levelWaitClock > 0){ levelWaitClock--; +#if MUSIC_VOLUME > 0 + if(levelWaitClock == 209 && !isAttract) + XGM2_play(treasureMusic); +#endif + if(levelWaitClock == 20) + PAL_fadeOut(0, 47, 20, TRUE); if(levelWaitClock == 0){ + statEnemiesKilled = levelEnemiesKilled; + statTreasures = collectedCount; levelClearing = TRUE; levelClearClock = 0; return; @@ -93,8 +154,10 @@ static void updateGame(){ gameOver = TRUE; XGM2_stop(); } else { - levelWaitClock = 240; + levelWaitClock = 210; killBullets = TRUE; + if(paused){ paused = FALSE; clearPause(); } + XGM2_stop(); } } updateTreasures(); diff --git a/src/player.h b/src/player.h index 3ce6011..4ab019d 100644 --- a/src/player.h +++ b/src/player.h @@ -1,4 +1,4 @@ -#define PLAYER_SPEED FIX32(6) +#define PLAYER_SPEED FIX32(5.5) #define PLAYER_ACCEL PLAYER_SPEED >> 4 @@ -6,16 +6,17 @@ #define PLAYER_BOUND_Y FIX32(PLAYER_OFF) #define PLAYER_BOUND_H FIX32(224 - PLAYER_OFF) -#define CAMERA_X FIX32(96) -#define CAMERA_W FIX32(224) +#define CAMERA_X FIX32(112) +#define CAMERA_W FIX32(208) -#define SHOT_INTERVAL 15 +#define SHOT_INTERVAL 20 +#define PLAYER_SHOT_SPEED FIX32(18) s16 shotClock; fix32 screenX; fix32 playerSpeed; -fix32 playerVelX; +fix32 playerVelX; static void movePlayer(){ player.vel.y = 0; @@ -24,7 +25,7 @@ static void movePlayer(){ if(ctrl.left || ctrl.right || ctrl.up || ctrl.down){ playerSpeed = PLAYER_SPEED; if(ctrl.left || ctrl.right){ - if(!ctrl.a) player.shotAngle = ctrl.left ? 512 : 0; + if(!ctrl.a) player.shotAngle = ctrl.left ? FIX16(180) : 0; targetVelX = ctrl.left ? -playerSpeed : playerSpeed; } @@ -43,8 +44,8 @@ static void movePlayer(){ player.vel.x = playerVelX; if(player.vel.x != 0 && player.vel.y != 0){ - player.vel.x = fix32Mul(player.vel.x, FIX32(0.707)); - player.vel.y = fix32Mul(player.vel.y, FIX32(0.707)); + player.vel.x = F32_mul(player.vel.x, FIX32(0.707)); + player.vel.y = F32_mul(player.vel.y, FIX32(0.707)); } player.pos.x += player.vel.x; @@ -62,10 +63,14 @@ static void movePlayer(){ } static void boundsPlayer(){ - if(player.pos.y < PLAYER_BOUND_Y) + playerScrollVelY = player.vel.y; + if(player.pos.y < PLAYER_BOUND_Y){ player.pos.y = PLAYER_BOUND_Y; - else if(player.pos.y > PLAYER_BOUND_H) + if(playerScrollVelY < 0) playerScrollVelY = 0; + } else if(player.pos.y > PLAYER_BOUND_H){ player.pos.y = PLAYER_BOUND_H; + if(playerScrollVelY > 0) playerScrollVelY = 0; + } if(player.pos.x >= GAME_WRAP){ player.pos.x -= GAME_WRAP; @@ -89,15 +94,18 @@ static void cameraPlayer(){ static void shootPlayer(){ if(ctrl.a && shotClock == 0){ + // fix32 bulletVelX = (player.shotAngle == 0 ? PLAYER_SHOT_SPEED : -PLAYER_SHOT_SPEED) + (player.vel.x * 3); struct bulletSpawner spawner = { .x = player.pos.x, .y = player.pos.y, - .anim = 0, - .speed = FIX32(24), + .anim = 12, + .speed = PLAYER_SHOT_SPEED, .angle = player.shotAngle, .player = TRUE }; + spawner.ints[5] = F32_toInt(player.shotAngle == 0 ? PLAYER_SHOT_SPEED : -PLAYER_SHOT_SPEED); void updater(s16 i){ + bullets[i].vel.x = FIX32(bullets[i].ints[5]) + (player.vel.x << 2); if(bullets[i].clock == 4) killBullet(i, TRUE); } spawnBullet(spawner, updater); @@ -106,6 +114,57 @@ static void shootPlayer(){ } else if(shotClock > 0) shotClock--; } +static s16 attractXClock = 0; +static s16 attractXState = 0; // 0=moving, 1=paused +static s16 attractXDir = 1; // 1=right, -1=left +static s16 attractYClock = 0; +static s16 attractTargetY = 112; + +static void moveAttract(){ + ctrl.left = ctrl.right = ctrl.up = ctrl.down = FALSE; + + // X: move toward nearest enemy, pause, repeat + attractXClock--; + if(attractXClock <= 0){ + if(attractXState == 0){ + // finished moving -- pause + attractXState = 1; + attractXClock = 20 + (random() % 30); // pause 20-49 frames + } else { + // finished pausing -- find nearest enemy and head toward it + attractXState = 0; + attractXClock = 55 + (random() % 55); // move 55-109 frames + attractXDir = 1; // default right if no enemies + fix32 bestDist = FIX32(9999); + for(s16 i = 0; i < ENEMY_COUNT; i++){ + if(!enemies[i].active) continue; + fix32 dx = getWrappedDelta(enemies[i].pos.x, player.pos.x); + fix32 dist = dx < 0 ? -dx : dx; + if(dist < bestDist){ + bestDist = dist; + attractXDir = (dx >= 0) ? 1 : -1; + } + } + } + } + ctrl.right = (attractXState == 0 && attractXDir == 1); + ctrl.left = (attractXState == 0 && attractXDir == -1); + + // Y: pick a new target every 60 frames, steer toward it with a soft dead zone + // Range [48, 176] gives 128px of vertical movement + attractYClock++; + if(attractYClock >= 60){ + attractYClock = 0; + attractTargetY = 48 + (random() % 128); + } + fix32 dy = FIX32(attractTargetY) - player.pos.y; + ctrl.down = (dy > FIX32(10)); + ctrl.up = (dy < FIX32(-10)); + + // Auto-shoot every few shot intervals + ctrl.a = ((clock % (SHOT_INTERVAL * 3)) < SHOT_INTERVAL); +} + void loadPlayer(){ player.shotAngle = 0; player.camera = 0; @@ -113,10 +172,12 @@ void loadPlayer(){ player.pos.y = FIX32(112); playerVelX = 0; player.lives = 3; + shotClock = isAttract ? SHOT_INTERVAL : 0; player.image = SPR_addSprite(&momoyoSprite, - fix32ToInt(player.pos.x) - PLAYER_OFF, - fix32ToInt(player.pos.y) - PLAYER_OFF, + F32_toInt(player.pos.x) - PLAYER_OFF, + F32_toInt(player.pos.y) - PLAYER_OFF, TILE_ATTR(PAL0, 0, 0, 0)); + SPR_setDepth(player.image, 0); } void updatePlayer(){ @@ -140,29 +201,35 @@ void updatePlayer(){ player.pos.y += (targetY - player.pos.y) >> 3; // keep sprite position in sync so it doesn't pop on reappear s16 sx = getScreenX(player.pos.x, player.camera); - s16 sy = fix32ToInt(player.pos.y); + s16 sy = F32_toInt(player.pos.y); SPR_setPosition(player.image, sx - PLAYER_OFF, sy - PLAYER_OFF); player.respawnClock--; if(player.respawnClock == 0){ SPR_setVisibility(player.image, VISIBLE); - player.recoveringClock = 240; + player.recoveringClock = 180; + player.recoverFlash = TRUE; killBullets = TRUE; } return; } if(player.recoveringClock > 0){ - if(player.recoveringClock % 10 == 1) + if(player.recoverFlash && player.recoveringClock % 10 == 1) SPR_setVisibility(player.image, player.recoveringClock % 20 == 1 ? VISIBLE : HIDDEN); player.recoveringClock--; if(player.recoveringClock == 0) SPR_setVisibility(player.image, VISIBLE); } + if(isAttract) moveAttract(); movePlayer(); boundsPlayer(); cameraPlayer(); shootPlayer(); s16 sx = getScreenX(player.pos.x, player.camera); - s16 sy = fix32ToInt(player.pos.y); + s16 sy = F32_toInt(player.pos.y); SPR_setPosition(player.image, sx - PLAYER_OFF, sy - PLAYER_OFF); + if(player.pendingShow){ + SPR_setVisibility(player.image, VISIBLE); + player.pendingShow = FALSE; + } } } \ No newline at end of file diff --git a/src/sfx.h b/src/sfx.h index 47209c4..8119bbb 100644 --- a/src/sfx.h +++ b/src/sfx.h @@ -1,105 +1,58 @@ -static s16 sfxShotClock; -static u16 sfxShotFreq; - -#define SFX_VOL 2 - void sfxPlayerShot(){ - sfxShotClock = 4; - sfxShotFreq = 150; - PSG_setEnvelope(2, SFX_VOL); - PSG_setFrequency(2, sfxShotFreq); + XGM2_playPCM(sfxSamplePlayerShot, sizeof(sfxSamplePlayerShot), SOUND_PCM_CH1); } -static s16 sfxEnemyShotClock; -static u16 sfxEnemyShotFreq; -static u8 sfxEnemyShotType; - -// high sharp zap - quick descending chirp void sfxEnemyShotA(){ if(player.recoveringClock > 0) return; - sfxEnemyShotClock = 3; - sfxEnemyShotFreq = 1200; - sfxEnemyShotType = 0; - PSG_setEnvelope(1, SFX_VOL); - PSG_setFrequency(1, sfxEnemyShotFreq); + XGM2_playPCM(sfxSampleBullet1, sizeof(sfxSampleBullet1), SOUND_PCM_CH2); } -// mid buzzy pulse - sits in the midrange void sfxEnemyShotB(){ if(player.recoveringClock > 0) return; - sfxEnemyShotClock = 5; - sfxEnemyShotFreq = 400; - sfxEnemyShotType = 1; - PSG_setEnvelope(1, SFX_VOL); - PSG_setFrequency(1, sfxEnemyShotFreq); + XGM2_playPCM(sfxSampleBullet2, sizeof(sfxSampleBullet2), SOUND_PCM_CH2); } -// quick rising ping - sweeps upward void sfxEnemyShotC(){ if(player.recoveringClock > 0) return; - sfxEnemyShotClock = 4; - sfxEnemyShotFreq = 300; - sfxEnemyShotType = 2; - PSG_setEnvelope(1, SFX_VOL); - PSG_setFrequency(1, sfxEnemyShotFreq); + XGM2_playPCM(sfxSampleBullet3, sizeof(sfxSampleBullet3), SOUND_PCM_CH2); } -static s16 sfxPickupClock; -static u16 sfxPickupFreq; - -void sfxPickup(){ - sfxPickupClock = 12; - sfxPickupFreq = 800; - PSG_setEnvelope(0, SFX_VOL); - PSG_setFrequency(0, sfxPickupFreq); -} - -static s16 sfxExpClock; - void sfxExplosion(){ - sfxExpClock = 18; - PSG_setNoise(PSG_NOISE_TYPE_WHITE, PSG_NOISE_FREQ_CLOCK2); - PSG_setEnvelope(3, SFX_VOL); + XGM2_playPCM(sfxSampleExplosion, sizeof(sfxSampleExplosion), SOUND_PCM_CH3); } -void updateSfx(){ - if(sfxExpClock > 0){ - sfxExpClock--; - PSG_setEnvelope(3, SFX_VOL + (18 - sfxExpClock) * (15 - SFX_VOL) / 18); - if(sfxExpClock == 0){ - PSG_setEnvelope(3, 15); - } - } - if(sfxEnemyShotClock > 0){ - sfxEnemyShotClock--; - if(sfxEnemyShotType == 0) sfxEnemyShotFreq -= 300; - else if(sfxEnemyShotType == 1) sfxEnemyShotFreq -= 50; - else sfxEnemyShotFreq += 150; - PSG_setFrequency(1, sfxEnemyShotFreq); - PSG_setEnvelope(1, SFX_VOL + (sfxEnemyShotType == 0 ? (3 - sfxEnemyShotClock) * (15 - SFX_VOL) / 3 : - sfxEnemyShotType == 1 ? (5 - sfxEnemyShotClock) * (15 - SFX_VOL) / 5 : - (4 - sfxEnemyShotClock) * (15 - SFX_VOL) / 4)); - if(sfxEnemyShotClock == 0){ - PSG_setEnvelope(1, 15); - } - } - if(sfxPickupClock > 0){ - sfxPickupClock--; - // rising staircase: jump up every 3 frames - if(sfxPickupClock % 3 == 0) sfxPickupFreq += 200; - PSG_setFrequency(0, sfxPickupFreq); - PSG_setEnvelope(0, SFX_VOL); - if(sfxPickupClock == 0){ - PSG_setEnvelope(0, 15); - } - } - if(sfxShotClock > 0){ - sfxShotClock--; - sfxShotFreq -= 30; - PSG_setFrequency(2, sfxShotFreq); - PSG_setEnvelope(2, SFX_VOL + (4 - sfxShotClock) * (15 - SFX_VOL) / 4); - if(sfxShotClock == 0){ - PSG_setEnvelope(2, 15); - } - } +void sfxPickup(){ + XGM2_playPCM(sfxSamplePickup, sizeof(sfxSamplePickup), SOUND_PCM_CH1); +} + +void sfxGraze(){ + XGM2_playPCM(sfxSampleGraze, sizeof(sfxSampleGraze), SOUND_PCM_CH1); +} + +void sfxStartGame(){ + XGM2_playPCM(sfxSampleStartGame, sizeof(sfxSampleStartGame), SOUND_PCM_CH1); +} + +void sfxPlayerHit(){ + XGM2_playPCM(sfxSamplePlayerHit, sizeof(sfxSamplePlayerHit), SOUND_PCM_CH3); +} + +void sfxGameOver(){ + XGM2_playPCM(sfxSampleGameOver, sizeof(sfxSampleGameOver), SOUND_PCM_CH1); +} + +void sfxMenuSelect(){ + XGM2_playPCM(sfxSampleMenuSelect, sizeof(sfxSampleMenuSelect), SOUND_PCM_CH2); +} + +void sfxMenuChoose(){ + XGM2_playPCM(sfxSampleMenuChoose, sizeof(sfxSampleMenuChoose), SOUND_PCM_CH2); +} + +void sfxCollectTreasure(){ + XGM2_playPCM(sfxSampleCollectTreasure, sizeof(sfxSampleCollectTreasure), SOUND_PCM_CH3); +} + +void sfxCollectAllTreasures(){ + XGM2_playPCM(sfxSampleCollectAllTreasures, sizeof(sfxSampleCollectAllTreasures), SOUND_PCM_CH3); } diff --git a/src/stage.h b/src/stage.h index 3b63da7..1108887 100644 --- a/src/stage.h +++ b/src/stage.h @@ -1,176 +1,191 @@ // ============================================================================= -// LEVEL DESIGN GUIDE +// THREAT POINT LEVEL GENERATION SYSTEM // ============================================================================= // -// Each level is a single struct defining what spawns. The level ends when all -// enemies are killed (enemyCount == 0). Treasures are bonus -- they don't affect -// level completion. +// Levels are procedurally generated using a threat point (TP) budget. +// Each enemy type has a TP cost, and the budget grows with level index. +// Enemy types unlock progressively. Compositions vary each playthrough +// (RNG seeded from title screen). // -// --- STRUCT FIELDS --- -// -// drones Number of Drone enemies (type 1). Bulk pressure enemy. -// Speed 2, homes toward player every 30 frames. -// Fires 1 aimed bullet every 40 frames (only on L2+, i.e. index >= 1). -// Use dronesShoot=FALSE on L1 to introduce them without bullets. -// Good range: 6-16. Above 14 gets chaotic. -// -// gunners Number of Gunner enemies (type 2). Danmaku / bullet geometry. -// Speed 0.5 (slow drift), only shoots when on screen. -// Pattern controlled by gunnerPattern field (see below). -// Good range: 0-6. Even 2-3 gunners create significant bullet density. -// -// hunters Number of Hunter enemies (type 3). Fast chaser, no shooting. -// Speed 5 (matches player!). Homes every frame. Pure body pressure. -// Very dangerous -- 2-3 is oppressive, 6 is near-impossible. -// Good range: 0-6. Introduce after players learn movement. -// -// builders Number of Builder enemies (type 4). Treasure abductor. -// Speed 0.7 (drift), 1.4 (seeking/carrying). Grabs walking treasures -// and flies upward. If it reaches the top, the treasure dies and a -// Gunner spawns in its place. Only 1 treasure can be carried at a time. -// Creates urgency -- player must choose between killing enemies -// and saving treasures. Good range: 0-2. -// -// bossHp If > 0, spawns a Boss enemy (type 5) with this many HP. -// Boss number is auto-calculated from level index (lvl / 4). -// Set to 0 for non-boss levels. Boss speed is 1, bounces around. -// Boss has multiple attack phases based on remaining HP. -// Typical values: 25, 50, 75, 100, 125. -// Other enemies can coexist with the boss (adds pressure). -// -// treasures Number of treasures to spawn. Distributed across 4 zones -// (2 per zone if >= 4 treasures, then 1 each for remainder). -// Walk along the ground, can be collected by player for 1000 pts -// (2000 if caught mid-fall after enemy drops them). -// Max 8 (TREASURE_COUNT). Usually just set to 8. -// -// gunnerPattern Controls what bullet pattern gunners use: -// 0 = Radial Burst: 8 bullets in a circle every 60 frames. -// Rotating start angle. Steady, predictable pressure. -// 1 = Aimed Fan: 5 bullets aimed at player, spread +-64, -// every 45 frames. More targeted/aggressive. -// 2 = Mix: each gunner randomly picks 0 or 1 at spawn. -// Creates varied, less predictable bullet fields. -// -// dronesShoot TRUE = drones fire aimed bullets (normal behavior on L2+). -// FALSE = drones still home toward player but never shoot. -// Only meaningful for the very first level as a gentle intro. -// (Drone shooting is also gated by level >= 1 in code, so -// L1 drones never shoot regardless of this flag.) -// -// --- LIMITS --- -// -// Total enemies: 24 slots (ENEMY_COUNT). drones+gunners+hunters+builders+boss -// must not exceed 24. If it does, excess enemies silently fail to spawn. -// -// Total treasures: 8 slots (TREASURE_COUNT). -// -// Bullet slots: 70. Heavy gunner/boss levels can fill this up. Player bullets -// get priority and evict enemy bullets when full. -// -// --- SPAWNING --- -// -// Enemies are distributed across 4 zones (each 512px of the 2048px world). -// Enemy i spawns in zone (i % 4). They never spawn within 240px of the player -// and maintain 64px minimum spacing from each other. -// -// Boss always spawns in zone 1. -// -// --- DESIGN TIPS --- -// -// - Drone-heavy levels (12-16) create constant movement pressure -// - Gunner-heavy levels (4-6) create bullet reading / dodging challenges -// - Hunter levels force the player to keep moving (anti-camping) -// - Builder levels force tough choices: kill builders or save treasures? -// - Combining hunters + gunners is very hard (dodge bullets while fleeing) -// - Boss levels with escort enemies (drones/gunners alongside boss) are -// harder than solo boss fights -// - A "farm" level (lots of drones, no gunners) gives score-building breathers -// - gunnerPattern 0 (radial) is easier to dodge than 1 (aimed fan) +// Boss levels occur every 3rd level (indices 2, 5, 8, 11, 14). +// Boss escorts use 40% of normal budget, limited to drones + builders. // // ============================================================================= -struct LevelDef { - u8 drones, gunners, hunters, builders; - u8 bossHp; - u8 treasures; - u8 gunnerPattern; - bool dronesShoot; +#define LEVEL_COUNT 15 +#define TP_POOL_SIZE 5 + +// pool index -> enemy type mapping +static const u8 poolTypeMap[TP_POOL_SIZE] = { + ENEMY_TYPE_TEST, // 0: Fairy + ENEMY_TYPE_DRONE, // 1: Drone + ENEMY_TYPE_GUNNER, // 2: Gunner + ENEMY_TYPE_HUNTER, // 3: Hunter + ENEMY_TYPE_BUILDER, // 4: Builder }; -// dr gn hn bl boss tre pat shoot -const struct LevelDef levels[20] = { - // Phase 1: "Immediate danger" (L1-L4) - { 8, 1, 0, 0, 0, 8, 0, FALSE }, // L1 - { 10, 2, 0, 0, 0, 8, 0, TRUE }, // L2 - { 12, 3, 0, 0, 0, 8, 1, TRUE }, // L3 - { 8, 0, 0, 0, 25, 8, 0, TRUE }, // L4 BOSS 1 +// TP costs per pool index +static const u8 typeCost[TP_POOL_SIZE] = { 5, 2, 4, 3, 3 }; +static const u8 typeWeight[TP_POOL_SIZE] = { 2, 8, 4, 3, 3 }; +static const u8 typeMaxCount[TP_POOL_SIZE] = { 3, 16, 6, 6, 2 }; +static const u8 typeMinCount[TP_POOL_SIZE] = { 0, 2, 0, 0, 0 }; - // Phase 2: "You can't save everything" (L5-L8) - { 10, 2, 0, 1, 0, 8, 0, TRUE }, // L5 - { 14, 3, 0, 1, 0, 8, 1, TRUE }, // L6 - { 10, 2, 0, 2, 0, 8, 2, TRUE }, // L7 - { 8, 0, 0, 1, 50, 8, 0, TRUE }, // L8 BOSS 2 +// Boss HP per boss number (0-4) +static const s16 bossHpTable[5] = { 24, 50, 75, 100, 125 }; - // Phase 3: "Geometry matters" (L9-L12) - { 8, 3, 4, 0, 0, 8, 1, TRUE }, // L9 - { 10, 2, 4, 0, 0, 8, 2, TRUE }, // L10 - { 12, 3, 3, 0, 0, 8, 1, TRUE }, // L11 - { 0, 2, 2, 0, 75, 8, 2, TRUE }, // L12 BOSS 3 +// Returns bitmask of unlocked pool indices for a given level +static u8 getUnlockedTypes(u8 lvl){ + u8 mask = 0; + // Drone always unlocked + mask |= (1 << 1); + // Gunner from L2 (index 1) + if(lvl >= 1) mask |= (1 << 2); + // Fairy + Builder from L4 (index 3) + if(lvl >= 3){ + mask |= (1 << 0); + mask |= (1 << 4); + } + // Hunter from L7 (index 6) + if(lvl >= 6) mask |= (1 << 3); + return mask; +} - // Phase 4: "Suffocation" (L13-L16) - { 14, 4, 0, 2, 0, 8, 2, TRUE }, // L13 - { 10, 0, 6, 0, 0, 8, 0, TRUE }, // L14 - { 12, 4, 2, 0, 0, 8, 1, TRUE }, // L15 - { 0, 3, 0, 1, 100, 8, 2, TRUE }, // L16 BOSS 4 +static u8 getTreasureCount(u8 lvl){ + if(lvl == 0) return 4; + if(lvl <= 2) return 6; + return 8; +} - // Phase 5: "Arcade cruelty" (L17-L20) - { 16, 0, 4, 0, 0, 8, 0, TRUE }, // L17 - { 14, 4, 4, 2, 0, 8, 2, TRUE }, // L18 - { 6, 2, 2, 1, 50, 8, 2, TRUE }, // L19 MINI-BOSS - { 4, 2, 2, 1, 125, 8, 2, TRUE }, // L20 BOSS 5 FINAL -}; +static void assignGunnerPatterns(u8 lvl){ + u8 pat; + if(lvl < 3) pat = 0; // Cycle 1: radial burst + else if(lvl < 6) pat = 1; // Cycle 2: aimed fan + else pat = 2; // Cycle 3+: mix -#define LEVEL_COUNT 20 + for(s16 i = 0; i < ENEMY_COUNT; i++){ + if(enemies[i].active && enemies[i].type == ENEMY_TYPE_GUNNER){ + if(pat == 2) + enemies[i].ints[0] = random() % 2; + else + enemies[i].ints[0] = pat; + } + } +} static void distributeEnemies(u8 type, u8 count){ for(u8 i = 0; i < count; i++){ - u8 zone = i % 4; + u8 zone = i % SECTION_COUNT; spawnEnemy(type, zone); } } +// Generate enemy counts into the provided array (indexed by pool index) +static void generateLevel(u8 lvl, u8* counts){ + for(u8 i = 0; i < TP_POOL_SIZE; i++) counts[i] = 0; + + // L1 special case: 4 non-shooting drones + if(lvl == 0){ + counts[1] = 4; + return; + } + + // Compute budget + u16 budget = 8 + (lvl * 4) + (lvl * lvl / 5); + budget = (budget * (90 + (random() % 21))) / 100; + + // Boss levels get 40% escort budget + if(isBossLevel(lvl)){ + budget = (budget * 40) / 100; + if(budget < 4) budget = 4; + } + + u8 unlocked = getUnlockedTypes(lvl); + u8 maxTotal = isBossLevel(lvl) ? ENEMY_COUNT - 1 : ENEMY_COUNT; // reserve 1 slot for boss + + // Apply minimum guarantees + for(u8 i = 0; i < TP_POOL_SIZE; i++){ + if((unlocked & (1 << i)) && typeMinCount[i] > 0){ + counts[i] = typeMinCount[i]; + u16 cost = typeMinCount[i] * typeCost[i]; + if(cost <= budget) budget -= cost; + else budget = 0; + } + } + + // Shopping loop + u16 safety = 0; + u8 totalEnemies = 0; + for(u8 i = 0; i < TP_POOL_SIZE; i++) totalEnemies += counts[i]; + + while(budget >= 2 && totalEnemies < maxTotal && safety < 100){ + safety++; + + // Boss escort restriction: only drones + builders + u8 escortMask = isBossLevel(lvl) ? ((1 << 1) | (1 << 4)) : 0xFF; + + // Build weighted pool of affordable, unlocked, non-maxed types + u16 totalWeight = 0; + for(u8 i = 0; i < TP_POOL_SIZE; i++){ + if(!(unlocked & (1 << i))) continue; + if(!(escortMask & (1 << i))) continue; + if(counts[i] >= typeMaxCount[i]) continue; + if(typeCost[i] > budget) continue; + totalWeight += typeWeight[i]; + } + if(totalWeight == 0) break; + + // Weighted random pick + u16 roll = random() % totalWeight; + u16 accum = 0; + u8 picked = 0xFF; + for(u8 i = 0; i < TP_POOL_SIZE; i++){ + if(!(unlocked & (1 << i))) continue; + if(!(escortMask & (1 << i))) continue; + if(counts[i] >= typeMaxCount[i]) continue; + if(typeCost[i] > budget) continue; + accum += typeWeight[i]; + if(roll < accum){ + picked = i; + break; + } + } + if(picked == 0xFF) break; + + counts[picked]++; + budget -= typeCost[picked]; + totalEnemies++; + } +} + void loadLevel(u8 lvl){ if(lvl >= LEVEL_COUNT) lvl = LEVEL_COUNT - 1; level = lvl; - const struct LevelDef* def = &levels[lvl]; + grazeCount = 0; - distributeEnemies(ENEMY_TYPE_DRONE, def->drones); - distributeEnemies(ENEMY_TYPE_GUNNER, def->gunners); - distributeEnemies(ENEMY_TYPE_HUNTER, def->hunters); - distributeEnemies(ENEMY_TYPE_BUILDER, def->builders); + // Generate enemy composition + u8 counts[TP_POOL_SIZE]; + generateLevel(lvl, counts); - // set gunner pattern based on level def - for(s16 i = 0; i < ENEMY_COUNT; i++){ - if(enemies[i].active && enemies[i].type == ENEMY_TYPE_GUNNER){ - if(def->gunnerPattern == 2) - enemies[i].ints[0] = random() % 2; - else - enemies[i].ints[0] = def->gunnerPattern; - } + // Spawn enemies by type + for(u8 i = 0; i < TP_POOL_SIZE; i++){ + if(counts[i] > 0) + distributeEnemies(poolTypeMap[i], counts[i]); } - if(def->bossHp > 0){ - pendingBossHp = def->bossHp; - pendingBossNum = lvl / 4; // L3=0, L7=1, L11=2, L15=3, L18+=4 + // Assign gunner patterns + assignGunnerPatterns(lvl); + + // Boss spawn + if(isBossLevel(lvl)){ + pendingBossNum = lvl / 3; if(pendingBossNum > 4) pendingBossNum = 4; - if(lvl == 18) pendingBossNum = 1; // L19 mini-boss reuses boss 2 + pendingBossHp = bossHpTable[pendingBossNum]; spawnEnemy(ENEMY_TYPE_BOSS, 1); } - // spawn treasures - u8 treasureToSpawn = def->treasures; - for(u8 zone = 0; zone < 4 && treasureToSpawn > 0; zone++){ + // Spawn treasures + u8 treasureToSpawn = getTreasureCount(lvl); + for(u8 zone = 0; zone < SECTION_COUNT && treasureToSpawn > 0; zone++){ u8 perZone = treasureToSpawn >= 4 ? 2 : 1; for(u8 h = 0; h < perZone && treasureToSpawn > 0; h++){ spawnTreasure(zone); @@ -178,6 +193,7 @@ void loadLevel(u8 lvl){ } } + loadBgPalette(lvl % 3); loadMap(); } diff --git a/src/starfield.h b/src/starfield.h new file mode 100644 index 0000000..58a847e --- /dev/null +++ b/src/starfield.h @@ -0,0 +1,107 @@ +#define STAR_TILE_I 290 // tile 290 = faded, 291 = full bright +#define STAR_COUNT 64 +#define STAR_SPEED FIX16(0.7) // base max speed in tiles/frame (try 0.3–1.5) + +#define STAR_FADE_FRAMES 5 // frames: tile 290 (faded) +#define STAR_MID_FRAMES 20 // frames: tile 291 (mid) +#define STAR_BRIGHT_FRAMES 40 // frames: tile 292 (bright); tile 293 (full) after this + +#define STAR_SCREEN_W 40 +#define STAR_SCREEN_H 28 +#define STAR_CENTER_X 20 +#define STAR_CENTER_Y 14 + +typedef struct { + fix16 x, y; + fix16 dx, dy; + u8 prevX, prevY; + u8 age; // frames since spawn; drives tile stage + u8 variant; // 0 = base tiles (290-293), 1 = alt color tiles (294-297) +} StarParticle; + +static StarParticle stars[STAR_COUNT]; +static u8 spawnCounter; +static u8 starVariant; + +static void spawnStar(u8 i){ + fix16 angleDeg = FIX16(random() % 360); + fix16 speed = F16_mul(STAR_SPEED, FIX16(0.25) + (fix16)((random() % 8) * FIX16(0.11))); + stars[i].x = FIX16(STAR_CENTER_X); + stars[i].y = FIX16(STAR_CENTER_Y); + stars[i].dx = F16_mul(F16_cos(angleDeg), speed); + stars[i].dy = F16_mul(F16_sin(angleDeg), speed); + stars[i].prevX = 255; + stars[i].prevY = 255; + stars[i].age = 0; + stars[i].variant = starVariant; + spawnCounter++; +} + +// Advance a star forward by a random number of frames (no VDP writes). +// If it exits the screen during pre-advance, respawn it from center. +static void preadvanceStar(u8 i){ + u8 frames = (u8)(random() % 90); + for(u8 f = 0; f < frames; f++){ + stars[i].x += stars[i].dx; + stars[i].y += stars[i].dy; + if(stars[i].age < STAR_BRIGHT_FRAMES) stars[i].age++; + s16 tx = F16_toInt(stars[i].x); + s16 ty = F16_toInt(stars[i].y); + if(tx < 0 || tx >= STAR_SCREEN_W || ty < 0 || ty >= STAR_SCREEN_H){ + spawnStar(i); + return; + } + } +} + +void loadStarfield(u8 variant){ + starVariant = variant; + VDP_loadTileSet(&starTiles, STAR_TILE_I, DMA); + // Reset BG_B scroll so stars appear at screen positions 0-39, 0-27 + VDP_setVerticalScroll(BG_B, 0); + s16 zeroScroll[28] = {0}; + VDP_setHorizontalScrollTile(BG_B, 0, zeroScroll, 28, DMA); + spawnCounter = 0; + for(u8 i = 0; i < STAR_COUNT; i++){ + spawnStar(i); + preadvanceStar(i); + } +} + +void clearStarfield(){ + // Erase any star tiles still on BG_B before the next level loads + for(u8 i = 0; i < STAR_COUNT; i++){ + if(stars[i].prevX != 255){ + VDP_setTileMapXY(BG_B, TILE_ATTR(0, 0, 0, 0), stars[i].prevX, stars[i].prevY); + stars[i].prevX = 255; + stars[i].prevY = 255; + } + } +} + +void updateStarfield(){ + for(u8 i = 0; i < STAR_COUNT; i++){ + if(stars[i].prevX != 255) + VDP_setTileMapXY(BG_B, TILE_ATTR(0, 0, 0, 0), stars[i].prevX, stars[i].prevY); + + stars[i].x += stars[i].dx; + stars[i].y += stars[i].dy; + if(stars[i].age < STAR_BRIGHT_FRAMES) stars[i].age++; + + s16 tx = F16_toInt(stars[i].x); + s16 ty = F16_toInt(stars[i].y); + + if(tx < 0 || tx >= STAR_SCREEN_W || ty < 0 || ty >= STAR_SCREEN_H){ + spawnStar(i); + continue; + } + + u16 base = STAR_TILE_I + stars[i].variant * 4; + u16 tileIdx = (stars[i].age < STAR_FADE_FRAMES) ? base : + (stars[i].age < STAR_MID_FRAMES) ? (base + 1) : + (stars[i].age < STAR_BRIGHT_FRAMES) ? (base + 2) : (base + 3); + VDP_setTileMapXY(BG_B, TILE_ATTR_FULL(PAL1, 0, 0, 0, tileIdx), (u16)tx, (u16)ty); + stars[i].prevX = (u8)tx; + stars[i].prevY = (u8)ty; + } +} diff --git a/src/start.h b/src/start.h index 13ce875..57dd91d 100644 --- a/src/start.h +++ b/src/start.h @@ -5,40 +5,75 @@ s16 startClock; -static void updateTransition(s16 startTime, bool last){ - if(startClock >= startTime && startClock < startTime + TRANS_TIME){ - switch(startClock - startTime){ - case 0: VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(PAL1, 1, 0, 0, START_I), 0, 0, START_W, START_H); break; - case 5: VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(PAL1, 1, 0, 0, START_I + 1), 0, 0, START_W, START_H); break; - case 10: VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(PAL1, 1, 0, 0, START_I + 2), 0, 0, START_W, START_H); break; - case 15: VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(PAL1, 1, 0, 0, START_I + 3), 0, 0, START_W, START_H); break; - case 20: VDP_clearTileMapRect(BG_A, 0, 0, START_W, START_H); break; - case TRANS_TIME - 20: if(!last){ - VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(PAL1, 1, 0, 0, START_I + 3), 0, 0, START_W, START_H); - } break; - case TRANS_TIME - 15: if(!last) VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(PAL1, 1, 0, 0, START_I + 2), 0, 0, START_W, START_H); break; - case TRANS_TIME - 10: if(!last) VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(PAL1, 1, 0, 0, START_I + 1), 0, 0, START_W, START_H); break; - case TRANS_TIME - 5: if(!last) VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(PAL1, 1, 0, 0, START_I), 0, 0, START_W, START_H); break; - } - } +static void drawStartSplash(){ + VDP_drawImageEx(BG_B, &startSplash1, TILE_ATTR_FULL(PAL0, 0, 0, 0, START_I + 256), 12, 6, 0, DMA); } -static void drawStartSplash(){ - VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(PAL1, 0, 0, 0, START_I), 0, 0, START_W, START_H); - VDP_drawImageEx(BG_B, &startSplash1, TILE_ATTR_FULL(PAL0, 0, 0, 0, START_I + 256), 13, 7, 0, DMA); -} +s16 startScroll; static void drawStartBg(){ VDP_clearTileMapRect(BG_B, 0, 0, START_W, START_H); - VDP_drawImageEx(BG_B, &startBigBg, TILE_ATTR_FULL(PAL1, 0, 0, 0, START_I + 16), 0, 0, 0, DMA); + VDP_drawImageEx(BG_B, &startBg1, TILE_ATTR_FULL(PAL1, 0, 0, 0, START_I + 16), 0, 0, 0, DMA); + VDP_drawImageEx(BG_B, &startBg2, TILE_ATTR_FULL(PAL1, 0, 0, 0, START_I + 16 + 196), 14, 0, 0, DMA); + VDP_drawImageEx(BG_B, &startBg3, TILE_ATTR_FULL(PAL1, 0, 0, 0, START_I + 16 + 196 + 196), 14 + 14, 0, 0, DMA); + VDP_drawImageEx(BG_B, &startBg4, TILE_ATTR_FULL(PAL1, 0, 0, 0, START_I + 16 + 196 + 196 + 168), 0, 14, 0, DMA); + VDP_drawImageEx(BG_B, &startBg5, TILE_ATTR_FULL(PAL1, 0, 0, 0, START_I + 16 + 196 + 196 + 168 + 196), 14, 14, 0, DMA); + VDP_drawImageEx(BG_B, &startBg6, TILE_ATTR_FULL(PAL1, 0, 0, 0, START_I + 16 + 196 + 196 + 168 + 196 + 196), 14 + 14, 14, 0, DMA); + startScroll = -160; + VDP_setVerticalScroll(BG_A, startScroll); +} + +// State variables +static s16 startState; // 0 = main menu, 1 = music room +static s16 menuCursor; // 0 = START GAME, 1 = MUSIC ROOM +static s16 musicCursor; // 0-2, highlighted track +static s16 musicPlaying; // -1 = none, 0-2 = playing track index +static s16 musicDelay; // frames to delay before starting new track +static bool playBgmStart; // TRUE to play bgmStart after delay (from exiting music room) +static bool attractPending; // TRUE once pre-attract fade-out starts (blocks input) + +static const char* trackNames[3] = { + "Title", + "Stage", + "Boss " +}; + +static const u8* musicTrackList[3]; + +#define MENU_X 14 +#define MENU_Y 16 + +static void drawStartMenuCursors(){ + VDP_drawText(menuCursor == 0 ? "%Start Game" : " Start Game", MENU_X, MENU_Y); + VDP_drawText(menuCursor == 1 ? "%Music Room" : " Music Room", MENU_X, MENU_Y + 2); +} + +static bool startMenuTopDrawn, startMenuMidDrawn, startMenuBottomDrawn; + +static void drawStartMenuTop(){ + VDP_drawImageEx(BG_A, &startLogo, TILE_ATTR_FULL(PAL0, 0, 0, 0, 1111), 7, 7, 0, DMA); + VDP_drawText(" DRAGON-EATING DESCENT II", 7, 12); + startMenuTopDrawn = TRUE; +} + +static void drawStartMenuMid(){ + drawStartMenuCursors(); + startMenuMidDrawn = TRUE; +} + +static void drawStartMenuBottom(){ + char hiScoreStr[SCORE_LENGTH]; + uintToStr(highScore, hiScoreStr, 1); + VDP_drawText("High", 2, 25); + VDP_drawText(hiScoreStr, 2 + 5, 25); + VDP_drawText("2026 Peace Research", 38 - 19, 25); + startMenuBottomDrawn = TRUE; } static void drawStartMenu(){ - // VDP_drawImageEx(BG_A, &startLogo, TILE_ATTR_FULL(PAL0, 0, 0, 0, START_I + 16 + 256), 26, 2, 0, DMA); - VDP_drawText(" PRESS ANY", 19, 18); - VDP_drawText(" BUTTON", 19, 19); - VDP_drawText(" T. BODDY", 19, 24); - VDP_drawText(" 02.2026", 19, 25); + drawStartMenuTop(); + drawStartMenuMid(); + drawStartMenuBottom(); } static void loadGameFromStart(){ @@ -49,12 +84,122 @@ static void loadGameFromStart(){ loadGame(); } -s16 startTime; -static void updateStartMenu(){ - if(startTime == 0 && (ctrl.start || ctrl.a || ctrl.b || ctrl.c)){ - XGM2_stop(); - startTime = 30; +static void loadAttractFromStart(){ + isAttract = TRUE; + attractClock = ATTRACT_DURATION; + loadGameFromStart(); +} + +#define MUSIC_NP_Y 25 +#define MUSIC_CTRL_Y 17 + +static void drawMusicRoomCursors(){ + for(s16 i = 0; i < 3; i++){ + char line[16]; + line[0] = (musicCursor == i) ? '%' : ' '; + line[1] = ' '; + const char* name = trackNames[i]; + for(s16 j = 0; j < 11; j++) line[1 + j] = name[j]; + line[13] = '\0'; + VDP_drawText(line, 16, 13 + i); } +} + +static void drawMusicRoomNowPlaying(){ + if(musicPlaying >= 0){ + char line[32]; + const char* prefix = " Now Playing: "; + for(s16 i = 0; i < 15; i++) line[i] = prefix[i]; + const char* name = trackNames[musicPlaying]; + for(s16 i = 0; i < 11; i++) line[15 + i] = name[i]; + line[26] = '\0'; + VDP_drawText(line, 9, MUSIC_NP_Y); + } else { + VDP_drawText(" ", 9, MUSIC_NP_Y); + } +} + +static void drawMusicRoom(){ + VDP_drawImageEx(BG_A, &musicroom, TILE_ATTR_FULL(PAL0, 0, 0, 0, 1111), 15, 8, 0, DMA); + drawMusicRoomCursors(); + VDP_drawText("[ Play/Stop ", 14, MUSIC_CTRL_Y); + VDP_drawText("] Back ", 14, MUSIC_CTRL_Y + 1); + drawMusicRoomNowPlaying(); +} + +static void enterMusicRoom(){ + XGM2_stop(); + startState = 1; + musicCursor = 0; + musicPlaying = -1; + VDP_clearTileMapRect(BG_A, 0, 0, START_W, START_H); + drawMusicRoom(); +} + +static void exitMusicRoom(){ + XGM2_stop(); + musicPlaying = -1; + musicDelay = 1; + playBgmStart = TRUE; + startState = 0; + startClock = TRANS_TIME + 41; + VDP_clearTileMapRect(BG_A, 0, 0, START_W, START_H); + drawStartMenu(); +} + +s16 startTime; + +static bool prevUp, prevDown, prevA, prevB, prevC, prevStart; + +static void updateStartMainMenu(){ + if(attractPending) return; + // handle music delay + if(musicDelay > 0){ + musicDelay--; + if(musicDelay == 0 && playBgmStart){ +#if MUSIC_VOLUME > 0 + XGM2_play(bgmStart); +#endif + playBgmStart = FALSE; + } + } + // Up/down cursor movement + bool curUp = ctrl.up; + bool curDown = ctrl.down; + if(curUp && !prevUp){ + menuCursor = (menuCursor == 0) ? 1 : 0; + drawStartMenuCursors(); + sfxMenuSelect(); + startClock = TRANS_TIME + 21; + } + if(curDown && !prevDown){ + menuCursor = (menuCursor == 0) ? 1 : 0; + drawStartMenuCursors(); + sfxMenuSelect(); + startClock = TRANS_TIME + 21; + } + prevUp = curUp; + prevDown = curDown; + + // Confirm selection + bool curA = ctrl.a; + bool curB = ctrl.b; + bool curSt = ctrl.start; + if((curA && !prevA) || (curSt && !prevStart)){ + if(startTime == 0){ + if(menuCursor == 0){ + startTime = 30; + sfxStartGame(); + PAL_fadeOut(0, 47, 20, TRUE); + } else { + enterMusicRoom(); + } + } + } + prevA = curA; + prevB = curB; + prevStart = curSt; + if(startTime > 0){ startTime--; if(startTime <= 0){ @@ -63,20 +208,110 @@ static void updateStartMenu(){ } } +static void updateMusicRoom(){ + // handle music delay + if(musicDelay > 0){ + musicDelay--; + if(musicDelay == 0 && musicPlaying >= 0){ +#if MUSIC_VOLUME > 0 + XGM2_play(musicTrackList[musicPlaying]); +#endif + } + } + bool curUp = ctrl.up; + bool curDown = ctrl.down; + bool curA = ctrl.a; + bool curB = ctrl.b; + bool curSt = ctrl.start; + + if(curUp && !prevUp){ + musicCursor = (musicCursor == 0) ? 2 : musicCursor - 1; + drawMusicRoomCursors(); + sfxMenuSelect(); + } + if(curDown && !prevDown){ + musicCursor = (musicCursor == 2) ? 0 : musicCursor + 1; + drawMusicRoomCursors(); + sfxMenuSelect(); + } + if((curA && !prevA) || (curSt && !prevStart)){ + if(musicPlaying == musicCursor){ + XGM2_stop(); + musicPlaying = -1; + } else { + XGM2_stop(); + musicDelay = 1; + musicPlaying = musicCursor; + } + drawMusicRoomNowPlaying(); + } + if(curB && !prevB){ + exitMusicRoom(); + } + + prevUp = curUp; + prevDown = curDown; + prevA = curA; + prevB = curB; + prevStart = curSt; +} + void loadStart(){ - VDP_loadTileSet(startFade1.tileset, START_I, DMA); - VDP_loadTileSet(startFade2.tileset, START_I + 1, DMA); - VDP_loadTileSet(startFade3.tileset, START_I + 2, DMA); - VDP_loadTileSet(startFade4.tileset, START_I + 3, DMA); + static const u16 palBlack[64] = {0}; + PAL_setColors(0, palBlack, 64, CPU); + getHighScore(); + musicTrackList[0] = bgmStart; + musicTrackList[1] = stageMusic; + musicTrackList[2] = bossMusic; + startState = 0; + menuCursor = 0; + musicCursor = 0; + musicPlaying = -1; + prevUp = FALSE; prevDown = FALSE; prevA = FALSE; + prevB = FALSE; prevC = FALSE; prevStart = FALSE; + attractPending = FALSE; + startMenuTopDrawn = FALSE; + startMenuMidDrawn = FALSE; + startMenuBottomDrawn = FALSE; drawStartSplash(); - XGM2_play(bgmStart); } void updateStart(){ - updateTransition(0, FALSE); - updateTransition(TRANS_TIME, TRUE); - if(startClock == TRANS_TIME) drawStartBg(); - else if(startClock == TRANS_TIME + 40) drawStartMenu(); - else if(startClock > TRANS_TIME + 40) updateStartMenu(); + if(startScroll < 0 && startClock >= TRANS_TIME){ + startScroll += 8; + // if(startScroll > 0) startScroll = 0; + VDP_setVerticalScroll(BG_A, startScroll); + } + + if(startClock == 0) + PAL_fadeIn(0, 15, font.palette->data, 20, TRUE); + if(startClock == TRANS_TIME - 20) + PAL_fadeOutAll(20, TRUE); + if(startClock == TRANS_TIME){ + drawStartBg(); +#if MUSIC_VOLUME > 0 + XGM2_play(bgmStart); +#endif + u16 menuPal[32]; + memcpy(menuPal, font.palette->data, 16 * sizeof(u16)); + memcpy(menuPal + 16, shadow.palette->data, 16 * sizeof(u16)); + PAL_fadeIn(0, 31, menuPal, 20, TRUE); + drawStartMenuTop(); + } + // stagger menu/bottom text to avoid plane-wrap visibility + if(startClock > TRANS_TIME){ + if(!startMenuMidDrawn && startScroll >= -104) drawStartMenuMid(); + if(!startMenuBottomDrawn && startScroll >= -32) drawStartMenuBottom(); + } + if(startClock > TRANS_TIME + 20){ + if(startState == 0) updateStartMainMenu(); + else updateMusicRoom(); + } + // Attract mode only from main menu + if(startState == 0 && !attractPending && startClock == TRANS_TIME + 20 + ATTRACT_LIMIT - 20 && startTime == 0){ + attractPending = TRUE; + PAL_fadeOut(0, 31, 20, TRUE); + } + if(startState == 0 && startClock >= TRANS_TIME + 20 + ATTRACT_LIMIT && startTime == 0) loadAttractFromStart(); if(startClock < CLOCK_LIMIT) startClock++; } diff --git a/src/treasure.h b/src/treasure.h index f737a3b..02b9cf7 100644 --- a/src/treasure.h +++ b/src/treasure.h @@ -19,12 +19,13 @@ void spawnTreasure(u8 zone){ treasures[i].vel.y = (random() % 2 == 0) ? FIX32(0.1) : FIX32(-0.1); treasures[i].image = SPR_addSprite(&treasureSprite, - getScreenX(treasures[i].pos.x, player.camera) - TREASURE_OFF, fix32ToInt(treasures[i].pos.y) - TREASURE_OFF, + getScreenX(treasures[i].pos.x, player.camera) - TREASURE_OFF, F32_toInt(treasures[i].pos.y) - TREASURE_OFF, TILE_ATTR(PAL0, 0, 0, 0)); if(!treasures[i].image){ treasures[i].active = FALSE; return; } + SPR_setDepth(treasures[i].image, 2); SPR_setVisibility(treasures[i].image, HIDDEN); treasures[i].type = random() % 4; SPR_setAnim(treasures[i].image, treasures[i].type); @@ -34,8 +35,13 @@ static void updateTreasure(u8 i){ switch(treasures[i].state){ case TREASURE_WALKING: // Y bounce: bob 4px around ground level - if(treasures[i].pos.y >= GAME_H_F - FIX32(20) || treasures[i].pos.y <= GAME_H_F - FIX32(28)) - treasures[i].vel.y *= -1; + if(treasures[i].pos.y >= GAME_H_F - FIX32(16)){ + if(treasures[i].vel.y > 0) treasures[i].vel.y = -treasures[i].vel.y; + treasures[i].pos.y = GAME_H_F - FIX32(16); + } else if(treasures[i].pos.y <= GAME_H_F - FIX32(32)){ + if(treasures[i].vel.y < 0) treasures[i].vel.y = -treasures[i].vel.y; + treasures[i].pos.y = GAME_H_F - FIX32(32); + } // X wrap if(treasures[i].pos.x >= GAME_WRAP) @@ -43,8 +49,8 @@ static void updateTreasure(u8 i){ if(treasures[i].pos.x < 0) treasures[i].pos.x += GAME_WRAP; - treasures[i].pos.x += treasures[i].vel.x; - treasures[i].pos.y += treasures[i].vel.y; + treasures[i].pos.x += treasures[i].vel.x - (player.vel.x >> 2); + treasures[i].pos.y += treasures[i].vel.y - (playerScrollVelY >> 3); break; case TREASURE_CARRIED: @@ -109,8 +115,20 @@ static void updateTreasure(u8 i){ if(treasures[i].state != TREASURE_CARRIED && treasures[i].state != TREASURE_COLLECTED){ fix32 dy = treasures[i].pos.y - player.pos.y; if(dx >= FIX32(-32) && dx <= FIX32(32) && dy >= FIX32(-32) && dy <= FIX32(32)){ - score += (treasures[i].state == TREASURE_FALLING) ? 2000 : 1000; - sfxPickup(); + score += (treasures[i].state == TREASURE_FALLING) ? 4096 : 1024; + // check if this is the last treasure (all others inactive or collected) + bool willBeLast = TRUE; + for(s16 j = 0; j < TREASURE_COUNT; j++){ + if(j != i && treasures[j].active && treasures[j].state != TREASURE_COLLECTED){ + willBeLast = FALSE; + break; + } + } + if(willBeLast){ + sfxCollectAllTreasures(); + } else { + sfxCollectTreasure(); + } treasureCollectedType = treasures[i].type; treasureCollectedClock = 120; // only add to trail if this type isn't already collected @@ -133,12 +151,12 @@ static void updateTreasure(u8 i){ } s16 sx = getScreenX(treasures[i].pos.x, player.camera); - s16 sy = fix32ToInt(treasures[i].pos.y); + s16 sy = F32_toInt(treasures[i].pos.y); bool visible = (treasures[i].state == TREASURE_COLLECTED) || (dx >= -CULL_LIMIT && dx <= CULL_LIMIT); if(visible && treasures[i].state == TREASURE_COLLECTED){ if(player.respawnClock > 0) visible = FALSE; - else if(player.recoveringClock > 0) + else if(player.recoveringClock > 0 && player.recoverFlash) visible = (player.recoveringClock % 20 > 10); } SPR_setVisibility(treasures[i].image, visible ? VISIBLE : HIDDEN);