From 49ed2d6f7bcd2c2ef653633a7fbef9c6ec68e91c Mon Sep 17 00:00:00 2001 From: Thomas Date: Sun, 1 Mar 2026 11:00:51 +0000 Subject: [PATCH] Add spatial audio, spacecraft entity, and level intro sequence Distance-based sound effects with stereo panning for explosions, impacts, and pickups. Spacecraft entity with full state machine (fly-in, land, take off, fly-out), engine/synth sound loops, thruster particles, and PNG spritesheet. Moon level intro defers player spawn until ship lands. Also untrack build/ objects that were committed by mistake. --- TODO.md | 30 ++- assets/levels/moon01.lvl | 2 + assets/sounds/engine.wav | Bin 0 -> 124418 bytes assets/sounds/powerUp.wav | Bin 0 -> 16762 bytes assets/sounds/synth.wav | Bin 0 -> 41752 bytes assets/sprites/spacecraft.png | Bin 0 -> 1575 bytes build/engine/animation.o | Bin 1872 -> 0 bytes build/engine/assets.o | Bin 5176 -> 0 bytes build/engine/audio.o | Bin 6224 -> 0 bytes build/engine/camera.o | Bin 3536 -> 0 bytes build/engine/core.o | Bin 6776 -> 0 bytes build/engine/entity.o | Bin 3088 -> 0 bytes build/engine/input.o | Bin 3480 -> 0 bytes build/engine/particle.o | Bin 8360 -> 0 bytes build/engine/physics.o | Bin 5072 -> 0 bytes build/engine/renderer.o | Bin 4136 -> 0 bytes build/engine/tilemap.o | Bin 7720 -> 0 bytes build/game/enemy.o | Bin 7992 -> 0 bytes build/game/level.o | Bin 12448 -> 0 bytes build/game/player.o | Bin 16096 -> 0 bytes build/game/projectile.o | Bin 12496 -> 0 bytes build/game/sprites.o | Bin 32976 -> 0 bytes build/main.o | Bin 3360 -> 0 bytes src/engine/audio.c | 73 +++++++ src/engine/audio.h | 20 ++ src/engine/entity.h | 1 + src/game/entity_registry.c | 3 + src/game/hazards.c | 4 +- src/game/level.c | 148 ++++++++++---- src/game/level.h | 4 + src/game/spacecraft.c | 372 ++++++++++++++++++++++++++++++++++ src/game/spacecraft.h | 70 +++++++ tools/gen_spacecraft.py | 360 ++++++++++++++++++++++++++++++++ 33 files changed, 1026 insertions(+), 61 deletions(-) create mode 100644 assets/sounds/engine.wav create mode 100644 assets/sounds/powerUp.wav create mode 100644 assets/sounds/synth.wav create mode 100644 assets/sprites/spacecraft.png delete mode 100644 build/engine/animation.o delete mode 100644 build/engine/assets.o delete mode 100644 build/engine/audio.o delete mode 100644 build/engine/camera.o delete mode 100644 build/engine/core.o delete mode 100644 build/engine/entity.o delete mode 100644 build/engine/input.o delete mode 100644 build/engine/particle.o delete mode 100644 build/engine/physics.o delete mode 100644 build/engine/renderer.o delete mode 100644 build/engine/tilemap.o delete mode 100644 build/game/enemy.o delete mode 100644 build/game/level.o delete mode 100644 build/game/player.o delete mode 100644 build/game/projectile.o delete mode 100644 build/game/sprites.o delete mode 100644 build/main.o create mode 100644 src/game/spacecraft.c create mode 100644 src/game/spacecraft.h create mode 100644 tools/gen_spacecraft.py diff --git a/TODO.md b/TODO.md index 7ece9f0..91249e8 100644 --- a/TODO.md +++ b/TODO.md @@ -1,9 +1,15 @@ # TODO -## Distance-based sound effects -Sounds (explosions, impacts, etc.) should attenuate based on distance from the -player. Offscreen events far away should be inaudible. Add a helper that takes -a world position and scales volume by proximity to the camera/player. +## ~~Distance-based sound effects~~ ✓ +Implemented: `audio_set_listener()`, `audio_play_sound_at()` with linear +attenuation and stereo panning. Used by enemy death, asteroid impact, and +powerup pickup. + +## ~~Spacecraft art and entity~~ ✓ +Implemented: spacecraft PNG spritesheet (5 frames, 80x48 each), full entity +with state machine (FLYING_IN → LANDING → LANDED → TAKEOFF → FLYING_OUT → +DONE), engine/synth sounds, thruster particles, level intro sequence with +deferred player spawn. ## Large map support (5000x5000) Audit the engine for anything that breaks or becomes slow at very large map @@ -15,18 +21,10 @@ sizes. Key areas to check: - Camera bounds and coordinate overflow (float precision at large coords) - Level file parsing (row lines could exceed fgets buffer at 5000+ columns) -## Spacecraft art and entity -Create placeholder pixel art for the player's spacecraft (fits the 16x16 grid -aesthetic but spans multiple cells, roughly 4-5 tiles wide by 2-3 tiles tall). -Needs at minimum: -- Landed/idle frame (visible near spawn on planet surface levels) -- Landing animation (descending + touchdown, 3-4 frames) -- Takeoff animation (liftoff + ascending, reuse landing in reverse) -Used in two contexts: -- **Level start**: ship flies in, lands at spawn, player exits, ship takes off - and leaves — serves as the level intro sequence -- **Level exit**: ship flies in and lands when player nears the exit zone, - player enters, ship takes off — triggers level transition +## Spacecraft at level exit +Ship landing at exit zone when player approaches — player enters, ship takes +off, triggers level transition. The intro/start sequence is done; this is the +exit counterpart. ## Asteroid refinement - Reduce asteroid count significantly — occasional hazard, not a barrage diff --git a/assets/levels/moon01.lvl b/assets/levels/moon01.lvl index 9071890..4adbda7 100644 --- a/assets/levels/moon01.lvl +++ b/assets/levels/moon01.lvl @@ -12,6 +12,8 @@ PARALLAX_STYLE 4 MUSIC assets/sounds/algardalgar.ogg PLAYER_UNARMED +ENTITY spacecraft 1 14 + ENTITY asteroid 66 1 ENTITY asteroid 70 1 ENTITY asteroid 74 1 diff --git a/assets/sounds/engine.wav b/assets/sounds/engine.wav new file mode 100644 index 0000000000000000000000000000000000000000..02db879ac33d4cfff20f8617f0a09cba9cf2b9d3 GIT binary patch literal 124418 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 literal 0 HcmV?d00001 diff --git a/assets/sounds/powerUp.wav b/assets/sounds/powerUp.wav new file mode 100644 index 0000000000000000000000000000000000000000..502eddbedb4bc22f7d9a68d6889ad341f8cf1b3a GIT binary patch literal 16762 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 literal 0 HcmV?d00001 diff --git a/build/engine/animation.o b/build/engine/animation.o deleted file mode 100644 index 5906b3dd2f22c8125ea5ff9d828a13af753c590e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1872 zcmbVM&1=*^6rZ$f8{3tnvY=A6D-}P$rkheJ>p=pwCc;vSv{10Jq-*+t-4Bxqw1Vx* z+QSkG#k=S~Ab9kcTCg6qC;x;>4=M{Dyy(2_o9z%54;^@U@BQBI&Ac}=nT?6u-4012 zd`RRrIr2D4$W-FUHX_p?NzzLi%Edocc8n@keazZ3We9yTRkEuLRKbqo!I-uAn@(3Z zRN_xIlvy*G3%VARANzqRHp5d>?$gVG zE3>jLSOrtIf*xxtQDyQX7Z59$l5_QtWsnIy)c2cq6=rGn(`{{rG!+!o$WK^-q3#lMeEe4W+RVIPqJCP}wNWiIr2CPpuaAsqnNe-zItB=V zZC9C`U}8?rkPbpZz&e@0NHrCao;@Jasw(v-yX9?G1LT~?*g4dIWzY@;jBQIavH~`X z_7&8y&I;OnsQ;&68xrK#-*%kNHaF}PBc2v;UUNUqqe@Lks_(Y)&vMBcKOU$H%!UvTZBNT<#! z^<4)_O2dZ@c8-xm4*QIv!SOWuRt)F;&c$%vZzYEFeu?I-6{$VXGUArNV+Jz_RFP40*;+89pOV^0zIxDv3EMd>M`tcQDk8s diff --git a/build/engine/assets.o b/build/engine/assets.o deleted file mode 100644 index d2cb0e0908a1ba31c6cd9ddc205b9a670dee8d49..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5176 zcmcImTWl0n7(Tn@Qd^v@c&nk%H33s1oph=aFQ~iF%T!jpl%f%FUAD7!q1)Ze&Jqfd zqNOBjDZ#`CAAG=QBEI?Lg+y5bhUkO(XzYV%)KDdg!4Tsm_5aSCLw|-{qY?jP=A84L z@BhyE|8toc>rV89Lm{CpB<>S)EsYAXvuUm#lhv4r2t%BYEc?wKKU;5II4TYZVVCbk zr#&T$Q}ZR0UT07_7S^>RVQ+ywezN|=A=zP1O8N&F*q*u`T)TW61gkQGR>q99?n6b6 z7vI!1c?TNtfxhye@r?uh-4hq~Jdv1M0nnOGl)wGqhDhsPw8l?_?B_mgCgrZX&U;p( z{G(m|Ne((uDSd6Hr|s-CdLNv8y#;}9PLxk?i9Z?N5`XGMq%|hSe;*s$t>#D0(zCC8 z(w?Y9_B~JfcKT`zV^7IYi81v^$j*KQ$+8UO=q$QJ$L;c`(m7H7{up(VhYTT6K8#_l zitOa0XCkd#MDtUlvLZ8NRl2QjBzJV)m6QitIC&6BF@Zy@L5ui5sB2 z^;^@F%dzGk68|!PeUtlI>XE;>W&YMDRLWJdsNNJ*(`gb}WT89ciWfJuB>qS#Q3~ z+1%;4YbhvWr{yWOE>u6jIFXyT$P)=Rtnb0zWZy{Ei)K>UVQ28}=!#;4h!=~F7ah*# z2zQ-SddL~Pxj`iIsX=Ej>Nz{TlI!?P(IefWUOqZZ-Mll}wxYPQL5SXU>+X)W4Qwrq zc%`UinYWuA?N&)Ptv#KcX2c33}E5CtE{&(PK0n4EGt$Q+3@&^uWBh z(YAkn+~^oz5MJM4v~?NLwMO$=qiL;C-`yC_2cX`@CrN_tOKIQY9niC(*CqAhC-p}(B86UYPE5MJFz)BbSWXdbVl*u#$*&2ghCE>nlz z%M7mb#Lu`KFSEL;Um&a@R`OD#4MOZW9BT!`-3vU5223S;8C8L6p7vZNN%F|ZntoXd zzf}XOT&-|<$FshBWPJfc{A!Gf#Vjtl#|TT%!#=Il@~xkWYB<)l8lJ6z7i!>p2*-TT z50iVE$`|RWrvGvc{74P_ts40Igk#*d)ZUSMldw-}@V`|2!}xa~)BJN(uQpHo%0T~+ zI`DFD689RytMQlDz(opoSkX+2V$yX+1|8RNsp_M;l1%66RuX1nT?byb;bq~~soaxU zx*A2!$&uE@t5WoLC6c|4*X_D_()38%70G0#;ATg>Ofo&RU3)*{cu93#63*q`Mp% zE?leQKp0QrFF&`&e!Dy zro;7LVmR(}t@9ew;rj0}K3}g>0bJ+p0@LB^wGxRTL*8zrM~~Z~@Zh@i)WA0}9iIOp zIw|NlBYw`utwns`{bJ z=l=`iL;GT$6(4z4OC*t4057Qap#ZMGo96=fVI}r3+y8*{cTkwc(VUmsN~P!e<&dtq z&Tz`acSe|lDK90=t;M1+-8>qgTPA(fc=JwSrt`U+gKytR-g8X)({3+%sq}W$erBX( z7TkQnalKJ;g}yVC%($tXqvrizizo>o)Zm|1j4;@*D*t}#@^3%(2-Ym7397Vl`H;1# zUqP%j#=+eQYxWCq3zfhF!-zZe22(s;sAbVjv5IQY4Eo2EfAqjbs;opWZ3_ICLQ{Vi zb+`7vUjwStEze!6^I`3Gp?(|?%1A#%j` zo1Y4eg~0<`VQjr~C_gqRJuYzngGSW2#>l1zdV$JN-ib!(dBkf4zUI zjSZKl)rNjoO$`4!jaB?{hX(f!L82KQWcq)=Z~3w6I?8M4spfx1&Hn{Xrg|V9;xE86 Bd9eTh diff --git a/build/engine/audio.o b/build/engine/audio.o deleted file mode 100644 index a538c69a06c4bb597a929a7065fe82649edfcb7b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6224 zcmb_gYit}>6~5~v?$#;eO>4lV&`eW1I7+(OaZ?DUX}$3yW7srpY$rj*+Kj!k?5g{) zc4vbfqSnY5Sb~xH(;qE@BDE4iyd~NO3A{8VMWP55e!w53@Ki!dMO9Iu5*p5T=ic4p z$xZ=@BhAdc-~G{co z9$4=sFk_$D+&@4KyFS|2Yc(!{xO|~^`5(RB4%xBK><9KExUg5>j<#L@v0|PTQ6YZN zf$`Xq5bLAg5JGCLG!`DQSEu@V>{YMNuvbs_#S~t5uiI<;h>z_IIJ~&aoq746p40{iz>c3b1 zRT_LxGQ^8A#H^l?a<6AwJhifiydZym(jv9p_R3Xn z=k(dDB=x^rzqIRbuA?n|)L&R>EN(+GrCjL^#!{;_v#DB8K;;U^smtFJKTRHh($1yvZpVrO) zGW3d5p9_MOzK|@EL-~dDL@DnLnbmU9HM8YP(ebKgsqC4~Nhe=$W()3+*Gg!%|hA9kh5AT1H0xx ze{~nJ9N0}`EM2Ts^K(O{L}Va&iZAaJ@~7NPm_4Nvv^QUH7XLeYzJJb_CQX`Frr?_S zs(IgZD%Bil#&pZwBa_xSs+$gn63RnyBV#o|!3s zyhH}X!I6<6b6|S5R`P15WySZ#6L(lO*|i=X9E>OS#s_z)0lcEkbYZ8&u6XdiX+gVe z1K1|IAbNX`oH`;R3$e&8H(VE8qiqx0`nx4Vh@0d#Y9u?ndsYVOE>m4{ddtZF_58n-C`HJgM|; zJSUCVr#ohhsD0i>YvjBUMVvoXoX7AyYQ#QbL>(iRHloyRRtjp%(xb2|^n_>(bBd;6t*%MSM1Z->dLv1Nf-Ie-pqbWWQ64 z1n|R(AJvIbi6W~8c{To^S3*e&lya*sCFYq^8|`y9tM#CzKue&5{i>guh4ePvH1QV*3xVKZJiI1fNlM zzN75O@0e_rLinc@|9-WR>2G*7g#WnW&ju!E^<8!!sL?A>a;th`hrfxgD>$f<`1`D1k5O|qKB)Lu_E^IjzlY)2{WY$?FW7$v z!yUy3j+&wQiwsA7)A(l?elx?rpm4;4YN7d$GaR49KmBQ4{i?#@7qv+9^}Y@}*D?IJ z)P@B4`V0!3=jU&dEpTqxkRM&Ufk)}memm(qguo8Z5B4<(@LTB8d`sc5!{_y3hNCuX z{v@;W9)_P_IM#sX7a7jy}!zVyuQWwJpQwcZ<3z2^E1Y$<5?5` zHRJR5_!{H$INxCS4b0B=zQ1b;LHe@fwq|Gmuq*BOpIP5b>e!}l=!`yuv!$nb59|0K)*?F@gN;rzYtV17A1 z9)ddz=kxj`!*?;iKW8}qE==M;5S9u}_V1SW7LIchBvPe| z0bHMd1p6LgLZ5Sb0=PcUh%*F$p2xBIkX%$h!LZlC4{8nsH5IaxF5m$-VjGOM z5%pS{Blu=uGfY3o5B4q61*Xpv)RfYz{3ikmI(`ig6T6+ht>%s2M_ja?ejcVlv=f{} z?PrvJ@b{(G)6e5dKcFuDr1XQh*3)>78aQY0ZxLhqy_x7R(eu~P5;b5ms2^1eY9Qb+ zsQ(GlZMS}gaR?-7$h#0&Kd9?kU$38bzM+!9q)7Iu_lV_qBV9p#oKb>nhaizdktewn Xh(FL3*n`{Y>pzG-C#|$@LH&ON9^cyh diff --git a/build/engine/camera.o b/build/engine/camera.o deleted file mode 100644 index b1e22cb493ec3553103a4377a01a212783ff5e2b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3536 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 diff --git a/build/engine/input.o b/build/engine/input.o deleted file mode 100644 index 4ee525c3eb6b1b094fac8729ba8d6cd1e595e426..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3480 zcmb`JU1%It6oBt0Ya82icTHOws5bLZCBb&sjd@5V%B)GufI(woLG)oV*`4jK-R!0_ zcWNS{E7^i;h{U4!;zJ$;6?`ZNA&L*Hk+c;GQuMikB0^OVArDoF=gge5SudORK`-2# zbH01dx##}O&AvK1{&-s?BDh7wqhiNmDn$3b9o-02L&U@Z@kjjFE%WkSJa|WSGj!dE z_f&%zjGraPd#=Am4?XGDnChK^&M>sYJ& z(X8G08Pi>;`#+_&&E@;h2uSLBYU|Spu*R3ejLVPU*m&qNzI-^e4xqT%ms_a}C523; zH=~J2W$1|Tqnpu#5p(fN=(jrLL+Pc?i|M6-x8iG^9o4Jp_&d?Wg)RT<_}ajc>Q(=m zQQeZ)A$TXmzh*AMda~w&;p+T7=Hgez^BX~N@t)i4A8Zk<6ZfYED+-E_uSEwpaygwh zkK+I9czi9$4ig%|d>gnKw`nJu#&Bz&f<6iTY3R7~N1^Y5-UnTb4G%x8^`4&bOVZa; zDSbdso=o|HnR=zaUr!F`{e3h5?osFo=vdL%lcz7NFy>ww8ziZLOp^XL%F$S2v27$a(e~@USRx(kNbe8go*^Hs8yn`z2bvIFB$2(` zf~~Y*?-T1JiLvXX2RluAD}nbpDt_N?HP+tp%f!9G9r|~>8_WoBjc^CagT%WPevCNQ zijDn~pkp4kSq>9NUu;-6Hr&eyoSudTG%ffmgar~gH;omL?M<9<36!ozRw4LI7l<3= z7@RBURp?wh4F%%jF8qUCIGz*EYtVxDPHYmNCeHWbTjCXk-ypuK@ZX5niSr$42OH=8 zhInJA3Z*$;>KWl#8Q*oCk_2tebn zBP`34cIK>=$)B}ycA-e&nR3bVXB|#PHHNA4PBBXZjZ(uArQ8sDd0%GBFXFwxwl@^` zJ_K(A-)BA-=6omcUBbqk?{RO48=Oqhukb&&!}grQ`OV2HoZqRU!vCT?ZxF{XHwK;g zyNW;GkN2AV<4yh_EB;*1Ckp3!K2tcq9A7l?{h)K6D+=d2PtiTac-&*=W5m^co>Vxc z6$OR!dTR>jdOlJ(*MC{zT#rT*VLjNn4UodzAp2RBQ{Lut#F%N~6~(_o{B4D=68})) zb>g2ZoZkVVdkeGDo&hP{Q07^K&&JlJGcSdnwWTfe8P5~CTMms(d3F}RSG`o0jt>7z zPX?bT*=I_=KIfL_99J$t3}$ljR?fBIYyCf(21#&f_&cDv8DxA%>*g{3PT9tH7thvU zKysZD_R?YEUBy)VJ@Y&80CE1^K;hyr#j(v&DMy@u0nDk!cT;>X2a@FZ8K*$Ooa%HM z0fQY$YKnadY>ZDTe465Qg>(HZAES81O}wcVL=GIs{*$2K8>_mn5YX6>q`LkWfMZTI z{t|&JcZkPZ*y{Yhr-|wmkLQoSb$EV0|5kHj$UGe=-yeK$u&eQyQ$2e?VwL_ecPSgD SnBtz@;xREa8H&bVHU1wSp^W$d diff --git a/build/engine/particle.o b/build/engine/particle.o deleted file mode 100644 index 93d62a8a7d3149e00013e011968fb6f1e049adb4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8360 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 h*kcC}OVJ}1wRvD1cSEVR#$LiwA_F#L%RkwU z2NR@h0fN=CMAf8i-zsg|_^@h~Hcfokhd2o&1j+)03TRXDr&KZGPa$;6fRX*qefQcg zR?;uK(!2NE-#zEtbI(8f&5fJE4VU(^mfI;*({w#Y*9fvkBh9T%)2as}OYE~Q z-%vmAr)il$hNwiGPm?}pZ~cgjDJ%49>ai8H>VtvQ7q16;ZdO6e=Pd(s?TTq?6cSjE zO;bbdQYshwWUvWsBFp)DX~8&5j;USmGIAi8|oM`c%} zAv%nJOsiKX;}pDc68HSmzDLNOC-WZl-Y5`vq$^KEtyJAASm3d$hpBHhqKP=X1&gfI zNcDy8Z)f`X!6p0o-I^~o6Qw%?)^UG{D}A}Z8u1r1+%pB&ln%g{Ccn<^Ec8`B+_Tx2 zdhAPG4W#Z^JtM9uX52rJxXvc~i)Dy@_8#=jQu$2sgPiVFPTf+mP`UR#D%r<+te#<) zuX?-JLx+DbJ3GrN3vG9?7p{YB;%GIFiYK2V^D6a@i6_JRA{_o~IO>_REDbjeL&B%R zF-rAgWM-(h#dg&NzC#)zOb1d!n9Yg$H%Z$QbDt^NEsyyi6c^rv&j`)|dTX9@EjH$`L z93fum;z#%kte#^ot9p|9y20X0J@BP&Bc>vrM_gThKC&n=IY)2uG;g=SJ$6~*hO>;+ zaix_`$ebL-n?;pkKS<^2*l+RjbK)+(8mXmBf*)~x_W|T3Cz7vv4}5d@3B=vn*S%6p zSb?75svo~$HCb<1TdZd8Be?QkRQ@>~tO3`Y7g8yw`f>kr@^P6}GJpKaEwT4u8Fve6 zd|Eh#8ctC8X!3Vx5s6>p%x(`d<(^}fY>yCG$BhBPQz8}7D#$=UZ7HhfTok>-NP?*> z&Ca19YQ1zEtz()7uN0zxz!eh|5_!a>?8sQoZ98Kj({v2?AQ#B6v-OJ5IRKp}D2Plf zMgD_r@B(yBg8d|g#>hx>+FceQFA8@=zCziD;Yne5JEJT zx%D7xz*(jDip>u7gBh^jf!^~Jnqc-{I@l?g$9UVpSZOn|y`S2aUMAzd!_v3Y!iL!a148m}FnIe~)!H z-XB6&)QiA;hRVm2FMz1kQKm`M1y?c{P~dtkD4PSjhT7%epx+Fj1nu98%7LM1)Kt4@ zKkJVMsC$5?(^2Xe#W%#8vpgYJMK4I!MydTXY)(=6Sn{Pn`i3Wv&UgTh;X4yZPkH!S z&;xAT*8=I=d~nSh5!2%Xm@AlJ6}BSKSTD7@=i|bDUdD)&OYSv6)_hIKMPV$UzfNOW4c5D z;%8;fkyqkMuR2^U@nQwY)s(pS338l0FLCyfb5x1V5>3ThopHiFa9-dO|l^;bcosDTW>M zU|pTt!-B_M&ij>(*uk;-9z} zs85k)Gbnr>EAnu3LNSa5@XZV0KXKsfTBRqk0RATj&MsH{e{tY!v%>%Gz@Kp7+}(Ka zyPm|S_#bg$VsO4M!>914nGu5*t`dc(WJ%`ZrzFmLo^kMBS%Ckp1LvAn`gK`&teF~+s!97YJC|8^U06P z`}6&!_G;ZE!dwAm* z`EK2dGuD|eUm@j39kNX3%kKilzO2FL95Zfv*DpcNAMO)MUd3g;yIEvL-ajKB%)jIO l<;yc$e(nI$KIi6%k#B##|F}oZCx1x((dZRga#Ozi{{SUuxC#IO diff --git a/build/engine/renderer.o b/build/engine/renderer.o deleted file mode 100644 index e01bdf321cd45147fbed1eb4aad41c3a6e27b974..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4136 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 diff --git a/build/game/enemy.o b/build/game/enemy.o deleted file mode 100644 index 65b38d274b8781d87e5c51fe1feff266a3cd21b1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7992 zcmb_heQ=b;8Q)wIJxshkG-#?QXAO8lxa1HOD%CrZgBLg~HHi&P=y4?PB`5jn-33Tz zNMaAro0zHNXw^>bFl9QmqqUu>Q~a3Xg&-eO96@XAVE>@BDyS7SmZ2i1zh~dw%jPv# z+i9Pf_ubv!{+?%_efHUX_mY87c$uT1fGH_px3h_sLK)j~^+Y`&ssYBCi;eMXKdmz| z=Z&F(#*>5_2WlFrY#QaWYMJN{HU#S%>WuUUUSlZIIL4>fR+Jmt-{RY5I>66dFop-5 zL!oeZ*z`VG#J3;9_+#8%nAscSr^nL=9qAJn(7u$cuE_j@SDhQhEXJV5@XdsWGw%vp z)(A#=pBG5ex?po~-6`sq^Kuu$y|W;#cJiwIe8&dicH`>uG2SSAMb~`dqg3Mw`)CV4 zxc3Am@(TBkkQ2T$n<~b5fMqF{&uu>hy-;cYb#nfLTZ&j}T6$3d-*yP*(|ZeybawsXqrLAS2r@X( zfIXFSsA4u0qo#Gpja=HrMBYur6I$?8(}D*00?<&nIYQ}D#;e3sHaX$G+8LoT)s5-E zZFJS3znQX7Er*k*e+SRWr`%cun9=f@F<`vC^&aE5Jvpwj*C=L$=->b;BKUs zNn^^~UwaHgz5$OK0XF^tgB|1E*Ll6r;#D8>9e=elP6DrQZsJv9zihe&RsYsYv$x!7;GiMNLgzI1TOlJO>Syb6OzcD3Kd_vOpu@f55WX%UZ| zRMq1_dA-W{@Mjpt|-dZ-zoPJ>vM{TGP$qsJe=-N?A9zcHLS9&8@9{1;1Z`B#ZE1OCmRO*%Z| z&QJ6BM>$f5J=8ynUE$O{UAu=q7Q#PcVtT4g6R(A z8dOCw_!tXSE|?J_xl$5s^|NAMbqEEV1ciCp>M&BvIF!YJTzh2(aY~_p!jDA zqX3bVSp_Tj-d(I?P`HTjDTukdN=C{(d-tpokE_cx7 z43%s^WffJ3>m?3^PvVY#flG0#mt1??sIN!%Rg*C><~<^&)c1hw>!v=$^Z|#XojR%S z$vk=$=-VgzcI5PyxysXpD_kDOJ+AUv5gO{6N=UVceKbB*U*VKDd&(Wkpvf`~O^3!; z*Z(Ue?vZqnFQ}8>1+YW;M54+n)XC0HT6c(8f0Rq`5MPPnF9$&UHR6*c*AiI0#MfVB zX^3@0H7fC*i!EIFDPk^miG{0oN>ro~1)}T|^_i154|EiL3co zC}YDCr)z{R4~a%`Zr`fDM1iMizQ5x^|bdT<1I<$U9>CSDIMP&?byhu9Zki% zyCO+5rM2W_Qt=M6Guji0CnL%3j(DqJNd0(ghpD$)&1k9}kxRu>eYub&<6SX}WJLvU zy=mzvAKT0nY)3c20P9I~-($AW_>p8!baNMLLz#%D%w)Sc$Qw{L?DFtbfs3fbxvD+vZ?T2p-w_} zTzC||n?MNR9FM|ZB@jaRwRjZ%Cjucf?kqeCzY_bBg!r`I6h2GCr)&5P8m^C9rQw%p z{983#_kTdcFW305N}T+>PQyRY_J~>)A%GOG`tLL<>$Ma{n;A+p~fdWivPKW(;pQS9v~8gy650g_$rCpCUL^%+qkXt0;V>hU4fkpI_a$!k=LwCk`?Fi)>;4?oaQz&3 zQ^R#XuafrxyPq31T=#RUhU@3>Z#2AGo7W2xr+Lx(%H?B+HU0vPf4RIr5bq`puhVed z{Gv6l!abtJ*pFqY@SRFYJI@JHzuzitxcdFpXv5X-x0DT6_fGm7 z28kM1-7lZE;p)D4(1xq~BJ(BtI#baNC{qcmY*)2}*%9@n%q=PA!+kNzd>fKU=1X)_ z!`G5b;jT8(z-`Uf(%spK3kLIbb*D@p{v0!3TvTMct*h6EJ5i6Bz~#s%jGOI|SR&eK zT66hd-X18^6yr&U@xRj31>CZE3arl#O5K@!uZ5tx%xx?fOTg z{z;p2cKt2TohpAAeMBK=tU}zCFF{@DtNfX2Y*Kly#8R~WMa9H@oX&r{KDlYn9nw4~ a7ihU{Vz%ipx&u$8e@6O$9~slcuKzEa%)`Y1 diff --git a/build/game/level.o b/build/game/level.o deleted file mode 100644 index 0dece012dfff07f5182c8b89500c48cd08217fb9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12448 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 #include +#include static bool s_initialized = false; +/* ── Listener state for spatial audio ────────────── */ +static Vec2 s_listener_pos = {0}; +static bool s_listener_set = false; + +/* Default max audible distance in pixels (~40 tiles) */ +#define AUDIO_DEFAULT_MAX_DIST 640.0f + bool audio_init(void) { /* Initialize MP3 (and OGG) decoder support */ int mix_flags = MIX_INIT_MP3 | MIX_INIT_OGG; @@ -66,9 +74,25 @@ void audio_play_sound(Sound s, int volume) { int ch = Mix_PlayChannel(-1, (Mix_Chunk *)s.chunk, 0); if (ch >= 0) { Mix_Volume(ch, volume); + Mix_SetPanning(ch, 255, 255); /* reset to center (no panning) */ } } +int audio_play_sound_loop(Sound s, int volume) { + if (!s_initialized || !s.chunk) return -1; + int ch = Mix_PlayChannel(-1, (Mix_Chunk *)s.chunk, -1); /* -1 = loop forever */ + if (ch >= 0) { + Mix_Volume(ch, volume); + Mix_SetPanning(ch, 255, 255); /* reset to center */ + } + return ch; +} + +void audio_stop_channel(int ch) { + if (!s_initialized || ch < 0) return; + Mix_HaltChannel(ch); +} + void audio_play_music(Music m, bool loop) { if (!s_initialized) { fprintf(stderr, "audio_play_music: audio not initialized\n"); @@ -99,11 +123,60 @@ void audio_set_music_volume(int volume) { Mix_VolumeMusic(volume); } +/* ── Positional / spatial audio ──────────────────── */ + +void audio_set_listener(Vec2 pos) { + s_listener_pos = pos; + s_listener_set = true; +} + +void audio_play_sound_at(Sound s, int base_volume, Vec2 world_pos, float max_dist) { + if (!s_initialized || !s.chunk) return; + + if (max_dist <= 0.0f) max_dist = AUDIO_DEFAULT_MAX_DIST; + + /* Fall back to non-positional if listener was never set */ + if (!s_listener_set) { + audio_play_sound(s, base_volume); + return; + } + + float dx = world_pos.x - s_listener_pos.x; + float dy = world_pos.y - s_listener_pos.y; + float dist = sqrtf(dx * dx + dy * dy); + + /* Inaudible beyond max distance */ + if (dist >= max_dist) return; + + /* Linear attenuation: full volume at dist=0, silent at max_dist */ + float atten = 1.0f - (dist / max_dist); + int vol = (int)(base_volume * atten); + if (vol <= 0) return; + if (vol > 128) vol = 128; + + int ch = Mix_PlayChannel(-1, (Mix_Chunk *)s.chunk, 0); + if (ch < 0) return; + + Mix_Volume(ch, vol); + + /* Stereo panning: center = 127, hard-left = 0, hard-right = 254. + * Pan linearly based on horizontal offset relative to half the + * viewport width (~half of SCREEN_WIDTH). */ + float half_screen = (float)SCREEN_WIDTH * 0.5f; + float pan_ratio = dx / half_screen; /* -1..+1 roughly */ + if (pan_ratio < -1.0f) pan_ratio = -1.0f; + if (pan_ratio > 1.0f) pan_ratio = 1.0f; + Uint8 pan_right = (Uint8)(127 + (int)(pan_ratio * 127.0f)); + Uint8 pan_left = 254 - pan_right; + Mix_SetPanning(ch, pan_left, pan_right); +} + void audio_shutdown(void) { if (!s_initialized) return; Mix_HaltMusic(); Mix_CloseAudio(); Mix_Quit(); s_initialized = false; + s_listener_set = false; printf("Audio shut down.\n"); } diff --git a/src/engine/audio.h b/src/engine/audio.h index 8df34a7..3e820f1 100644 --- a/src/engine/audio.h +++ b/src/engine/audio.h @@ -2,6 +2,7 @@ #define JNR_AUDIO_H #include +#include "util/vec2.h" typedef struct Sound { void *chunk; /* Mix_Chunk* */ @@ -21,4 +22,23 @@ void audio_free_music(Music *m); void audio_set_music_volume(int volume); /* 0-128 */ void audio_shutdown(void); +/* ── Looping sounds ────────────────────────────── */ + +/* Play a sound on loop, returns the channel number (or -1 on failure). + * Use audio_stop_channel() to stop it later. */ +int audio_play_sound_loop(Sound s, int volume); + +/* Stop a specific channel (from audio_play_sound_loop). No-op if ch < 0. */ +void audio_stop_channel(int ch); + +/* ── Positional / spatial audio ────────────────── */ + +/* Set the listener position (call once per frame, typically from camera center) */ +void audio_set_listener(Vec2 pos); + +/* Play a sound at a world position. Volume is attenuated by distance from the + * listener and stereo-panned based on horizontal offset. Sounds beyond + * max_dist are inaudible. Pass max_dist <= 0 for a sensible default. */ +void audio_play_sound_at(Sound s, int base_volume, Vec2 world_pos, float max_dist); + #endif /* JNR_AUDIO_H */ diff --git a/src/engine/entity.h b/src/engine/entity.h index 0b58bea..ff199eb 100644 --- a/src/engine/entity.h +++ b/src/engine/entity.h @@ -27,6 +27,7 @@ typedef enum EntityType { ENT_POWERUP, ENT_DRONE, ENT_ASTEROID, + ENT_SPACECRAFT, ENT_TYPE_COUNT } EntityType; diff --git a/src/game/entity_registry.c b/src/game/entity_registry.c index f1aecb3..5ed6a55 100644 --- a/src/game/entity_registry.c +++ b/src/game/entity_registry.c @@ -5,6 +5,7 @@ #include "game/hazards.h" #include "game/powerup.h" #include "game/drone.h" +#include "game/spacecraft.h" #include #include @@ -67,6 +68,7 @@ void entity_registry_init(EntityManager *em) { powerup_register(em); drone_register(em); asteroid_register(em); + spacecraft_register(em); /* ════════════════════════════════════════════ * REGISTRY TABLE @@ -92,6 +94,7 @@ void entity_registry_init(EntityManager *em) { reg_add("powerup_drone", "Drone Pickup", spawn_powerup_drone, (SDL_Color){80, 200, 255, 255}, 12, 12); reg_add("powerup_gun", "Gun Pickup", spawn_powerup_gun, (SDL_Color){200, 200, 220, 255}, 12, 12); reg_add("asteroid", "Asteroid", asteroid_spawn, (SDL_Color){140, 110, 80, 255}, ASTEROID_WIDTH, ASTEROID_HEIGHT); + reg_add("spacecraft", "Spacecraft", spacecraft_spawn, (SDL_Color){187, 187, 187, 255}, SPACECRAFT_WIDTH, SPACECRAFT_HEIGHT); printf("Entity registry: %d types registered\n", g_entity_registry.count); } diff --git a/src/game/hazards.c b/src/game/hazards.c index 3e54fbd..8e70d4a 100644 --- a/src/game/hazards.c +++ b/src/game/hazards.c @@ -702,7 +702,7 @@ static void asteroid_update(Entity *self, float dt, const Tilemap *map) { particle_emit_death_puff(hit_pos, (SDL_Color){140, 110, 80, 255}); if (s_asteroid_sfx_loaded) { - audio_play_sound(s_sfx_asteroid_impact, 80); + audio_play_sound_at(s_sfx_asteroid_impact, 80, hit_pos, 0); } /* Explode on contact — hide and start respawn */ @@ -729,7 +729,7 @@ static void asteroid_update(Entity *self, float dt, const Tilemap *map) { particle_emit_death_puff(impact_pos, (SDL_Color){140, 110, 80, 255}); if (s_asteroid_sfx_loaded) { - audio_play_sound(s_sfx_asteroid_impact, 60); + audio_play_sound_at(s_sfx_asteroid_impact, 60, impact_pos, 0); } /* Hide and start respawn timer */ diff --git a/src/game/level.c b/src/game/level.c index ed5f784..dc4ae47 100644 --- a/src/game/level.c +++ b/src/game/level.c @@ -5,6 +5,7 @@ #include "game/hazards.h" #include "game/powerup.h" #include "game/drone.h" +#include "game/spacecraft.h" #include "game/sprites.h" #include "game/entity_registry.h" #include "engine/core.h" @@ -36,7 +37,7 @@ static bool level_setup(Level *level) { if (!s_sfx_loaded) { s_sfx_hit = audio_load_sound("assets/sounds/hitHurt.wav"); s_sfx_enemy_death = audio_load_sound("assets/sounds/teleport.wav"); - s_sfx_pickup = audio_load_sound("assets/sounds/teleport.wav"); + s_sfx_pickup = audio_load_sound("assets/sounds/powerUp.wav"); s_sfx_loaded = true; } @@ -92,24 +93,31 @@ static bool level_setup(Level *level) { (float)(level->map.width * TILE_SIZE), (float)(level->map.height * TILE_SIZE)); - /* Spawn player at map spawn point */ - Entity *player = player_spawn(&level->entities, level->map.player_spawn); - if (!player) { - fprintf(stderr, "Failed to spawn player!\n"); - return false; - } - - /* Disarm player if level requests it */ - if (level->map.player_unarmed) { - PlayerData *ppd = (PlayerData *)player->data; - if (ppd) ppd->has_gun = false; - } - /* Spawn entities from level data (via registry) */ + level->has_intro_ship = false; for (int i = 0; i < level->map.entity_spawn_count; i++) { EntitySpawn *es = &level->map.entity_spawns[i]; Vec2 pos = vec2(es->x, es->y); entity_registry_spawn(&level->entities, es->type_name, pos); + if (strcmp(es->type_name, "spacecraft") == 0) { + level->has_intro_ship = true; + } + } + + /* Spawn player — deferred if the level has an intro spacecraft */ + if (level->has_intro_ship) { + level->player_spawned = false; + } else { + Entity *player = player_spawn(&level->entities, level->map.player_spawn); + if (!player) { + fprintf(stderr, "Failed to spawn player!\n"); + return false; + } + if (level->map.player_unarmed) { + PlayerData *ppd = (PlayerData *)player->data; + if (ppd) ppd->has_gun = false; + } + level->player_spawned = true; } /* Load level music (playback deferred to first update — @@ -184,7 +192,7 @@ static void damage_entity(Entity *target, int damage) { camera_shake(s_active_camera, 2.0f, 0.15f); } - audio_play_sound(s_sfx_enemy_death, 80); + audio_play_sound_at(s_sfx_enemy_death, 80, center, 0); } } @@ -342,7 +350,7 @@ static void handle_collisions(EntityManager *em) { a->body.pos.y + a->body.size.y * 0.5f ); particle_emit_spark(center, (SDL_Color){255, 255, 100, 255}); - audio_play_sound(s_sfx_pickup, 80); + audio_play_sound_at(s_sfx_pickup, 80, center, 0); /* Destroy the powerup */ a->flags |= ENTITY_DEAD; @@ -403,46 +411,100 @@ void level_update(Level *level, float dt) { /* Set camera pointer for collision shake triggers */ s_active_camera = &level->camera; + /* ── Deferred player spawn: wait for spacecraft to land ── */ + if (level->has_intro_ship && !level->player_spawned) { + /* Find the spacecraft and check if it has landed */ + for (int i = 0; i < level->entities.count; i++) { + Entity *e = &level->entities.entities[i]; + if (e->active && e->type == ENT_SPACECRAFT && + spacecraft_is_landed(e)) { + /* Ship has landed — spawn the player at spawn point */ + Entity *player = player_spawn(&level->entities, + level->map.player_spawn); + if (player) { + if (level->map.player_unarmed) { + PlayerData *ppd = (PlayerData *)player->data; + if (ppd) ppd->has_gun = false; + } + level->player_spawned = true; + + /* Trigger takeoff now that the player has exited */ + spacecraft_takeoff(e); + } + break; + } + } + } + /* Update all entities */ entity_update_all(&level->entities, dt, &level->map); - /* Handle collisions */ - handle_collisions(&level->entities); + /* Handle collisions (only meaningful once player exists) */ + if (level->player_spawned) { + handle_collisions(&level->entities); - /* Check exit zones */ - check_exit_zones(level); + /* Check exit zones */ + check_exit_zones(level); - /* Check for player respawn */ - for (int i = 0; i < level->entities.count; i++) { - Entity *e = &level->entities.entities[i]; - if (e->active && e->type == ENT_PLAYER && player_wants_respawn(e)) { - player_respawn(e, level->map.player_spawn); - /* Re-snap camera to player immediately */ - Vec2 center = vec2( - e->body.pos.x + e->body.size.x * 0.5f, - e->body.pos.y + e->body.size.y * 0.5f - ); - camera_follow(&level->camera, center, vec2_zero(), dt); - break; + /* Check for player respawn */ + for (int i = 0; i < level->entities.count; i++) { + Entity *e = &level->entities.entities[i]; + if (e->active && e->type == ENT_PLAYER && player_wants_respawn(e)) { + player_respawn(e, level->map.player_spawn); + Vec2 center = vec2( + e->body.pos.x + e->body.size.x * 0.5f, + e->body.pos.y + e->body.size.y * 0.5f + ); + camera_follow(&level->camera, center, vec2_zero(), dt); + break; + } } } /* Update particles */ particle_update(dt); - /* Find player for camera tracking */ - for (int i = 0; i < level->entities.count; i++) { - Entity *e = &level->entities.entities[i]; - if (e->active && e->type == ENT_PLAYER) { - float look_offset = player_get_look_up_offset(e); - Vec2 center = vec2( - e->body.pos.x + e->body.size.x * 0.5f, - e->body.pos.y + e->body.size.y * 0.5f + look_offset - ); - camera_follow(&level->camera, center, e->body.vel, dt); - break; + /* Camera tracking — follow player if spawned, otherwise follow spacecraft */ + bool cam_tracked = false; + if (level->player_spawned) { + for (int i = 0; i < level->entities.count; i++) { + Entity *e = &level->entities.entities[i]; + if (e->active && e->type == ENT_PLAYER) { + float look_offset = player_get_look_up_offset(e); + Vec2 center = vec2( + e->body.pos.x + e->body.size.x * 0.5f, + e->body.pos.y + e->body.size.y * 0.5f + look_offset + ); + camera_follow(&level->camera, center, e->body.vel, dt); + cam_tracked = true; + break; + } } } + if (!cam_tracked) { + /* Follow the spacecraft during intro */ + for (int i = 0; i < level->entities.count; i++) { + Entity *e = &level->entities.entities[i]; + if (e->active && e->type == ENT_SPACECRAFT) { + Vec2 center = vec2( + e->body.pos.x + e->body.size.x * 0.5f, + e->body.pos.y + e->body.size.y * 0.5f + ); + camera_follow(&level->camera, center, vec2_zero(), dt); + cam_tracked = true; + break; + } + } + } + + /* Set audio listener to camera center */ + if (cam_tracked) { + Vec2 listener = vec2( + level->camera.pos.x + level->camera.viewport.x * 0.5f, + level->camera.pos.y + level->camera.viewport.y * 0.5f + ); + audio_set_listener(listener); + } /* Update screen shake */ camera_update_shake(&level->camera, dt); diff --git a/src/game/level.h b/src/game/level.h index 0e4c070..0188939 100644 --- a/src/game/level.h +++ b/src/game/level.h @@ -18,6 +18,10 @@ typedef struct Level { /* ── Exit / transition state ─────────── */ bool exit_triggered; /* player entered an exit zone */ char exit_target[ASSET_PATH_MAX]; /* target level path */ + + /* ── Intro sequence state ────────────── */ + bool has_intro_ship; /* level has a spacecraft intro */ + bool player_spawned; /* player has been spawned */ } Level; bool level_load(Level *level, const char *path); diff --git a/src/game/spacecraft.c b/src/game/spacecraft.c new file mode 100644 index 0000000..ba7e0d1 --- /dev/null +++ b/src/game/spacecraft.c @@ -0,0 +1,372 @@ +#include "game/spacecraft.h" +#include "engine/assets.h" +#include "engine/renderer.h" +#include "engine/particle.h" +#include "engine/audio.h" +#include "config.h" +#include +#include + +/* ═══════════════════════════════════════════════════ + * Constants + * ═══════════════════════════════════════════════════ */ + +#define SC_FLY_SPEED 180.0f /* px/s during fly-in / fly-out */ +#define SC_LAND_DURATION 1.6f /* seconds for the landing sequence */ +#define SC_TAKEOFF_DURATION 1.2f /* seconds for the takeoff sequence */ + +/* Spritesheet frame indices (each 80x48, laid out horizontally) */ +#define SC_FRAME_LANDED 0 +#define SC_FRAME_LAND1 1 +#define SC_FRAME_LAND2 2 +#define SC_FRAME_LAND3 3 +#define SC_FRAME_LAND4 4 +#define SC_FRAME_COUNT 5 + +/* How far off-screen the ship starts / exits */ +#define SC_OFFSCREEN_DIST 200.0f + +/* ═══════════════════════════════════════════════════ + * Static state + * ═══════════════════════════════════════════════════ */ + +static EntityManager *s_em = NULL; +static SDL_Texture *s_texture = NULL; +static bool s_texture_loaded = false; + +/* ── Sound effects ──────────────────────────────── */ +static Sound s_sfx_engine; +static Sound s_sfx_synth; +static bool s_sfx_loaded = false; + +static void load_sfx(void) { + if (s_sfx_loaded) return; + s_sfx_engine = audio_load_sound("assets/sounds/engine.wav"); + s_sfx_synth = audio_load_sound("assets/sounds/synth.wav"); + s_sfx_loaded = true; +} + +/* ═══════════════════════════════════════════════════ + * Helpers + * ═══════════════════════════════════════════════════ */ + +static void load_texture(void) { + if (s_texture_loaded) return; + s_texture = assets_get_texture("assets/sprites/spacecraft.png"); + s_texture_loaded = true; +} + +/* Get the source rect for a given frame index (in PNG pixel coords) */ +static SDL_Rect frame_rect(int frame_idx) { + return (SDL_Rect){ + frame_idx * SPACECRAFT_SRC_W, 0, + SPACECRAFT_SRC_W, SPACECRAFT_SRC_H + }; +} + +/* Pick the landing animation frame based on progress (0.0 = start, 1.0 = landed) */ +static int landing_frame(float progress) { + if (progress < 0.25f) return SC_FRAME_LAND1; + if (progress < 0.50f) return SC_FRAME_LAND2; + if (progress < 0.75f) return SC_FRAME_LAND3; + if (progress < 0.95f) return SC_FRAME_LAND4; + return SC_FRAME_LANDED; +} + +/* Pick the takeoff animation frame (reverse of landing) */ +static int takeoff_frame(float progress) { + return landing_frame(1.0f - progress); +} + +/* Emit thruster particles behind the ship */ +static void emit_thruster_particles(Vec2 ship_pos, float intensity) { + if (intensity <= 0.0f) return; + + /* Particles emit from the left (rear) center of the ship */ + Vec2 origin = vec2( + ship_pos.x + 8.0f, + ship_pos.y + SPACECRAFT_HEIGHT * 0.5f + ); + + int count = (int)(intensity * 3.0f); + if (count < 1) count = 1; + + ParticleBurst b = { + .origin = origin, + .count = count, + .speed_min = 30.0f * intensity, + .speed_max = 80.0f * intensity, + .life_min = 0.1f, + .life_max = 0.3f, + .size_min = 1.0f, + .size_max = 2.5f, + .spread = 0.6f, + .direction = 3.14159f, /* leftward */ + .drag = 2.0f, + .gravity_scale = 0.0f, + .color = (SDL_Color){255, 180, 50, 200}, + .color_vary = true, + }; + particle_emit(&b); +} + +/* ═══════════════════════════════════════════════════ + * Entity callbacks + * ═══════════════════════════════════════════════════ */ + +/* Start the engine loop sound, stopping any previous one */ +static void engine_start(SpacecraftData *sd) { + if (sd->engine_channel >= 0) return; /* already playing */ + sd->engine_channel = audio_play_sound_loop(s_sfx_engine, 70); +} + +/* Stop the engine loop sound */ +static void engine_stop(SpacecraftData *sd) { + if (sd->engine_channel < 0) return; + audio_stop_channel(sd->engine_channel); + sd->engine_channel = -1; +} + +static void spacecraft_update(Entity *self, float dt, const Tilemap *map) { + (void)map; + SpacecraftData *sd = (SpacecraftData *)self->data; + if (!sd) return; + + sd->state_timer += dt; + + switch (sd->state) { + + case SC_FLYING_IN: { + /* Move from start_pos toward target_pos */ + Vec2 dir = vec2_sub(sd->target_pos, sd->start_pos); + float total_dist = vec2_len(dir); + if (total_dist < 1.0f) { + /* Already at target */ + engine_stop(sd); + sd->state = SC_LANDING; + sd->state_timer = 0.0f; + self->body.pos = sd->target_pos; + break; + } + Vec2 norm = vec2_scale(dir, 1.0f / total_dist); + + float traveled = sd->state_timer * sd->fly_speed; + if (traveled >= total_dist) { + /* Arrived at landing zone */ + engine_stop(sd); + self->body.pos = sd->target_pos; + sd->state = SC_LANDING; + sd->state_timer = 0.0f; + } else { + self->body.pos = vec2_add(sd->start_pos, + vec2_scale(norm, traveled)); + emit_thruster_particles(self->body.pos, 1.0f); + } + break; + } + + case SC_LANDING: { + float progress = sd->state_timer / SC_LAND_DURATION; + if (progress >= 1.0f) { + progress = 1.0f; + self->body.pos = sd->target_pos; + sd->state = SC_LANDED; + sd->state_timer = 0.0f; + /* Play synth on touchdown */ + audio_play_sound(s_sfx_synth, 80); + } else { + /* Ease down from slightly above target to target */ + float height_offset = (1.0f - progress) * 30.0f; + self->body.pos = vec2( + sd->target_pos.x, + sd->target_pos.y - height_offset + ); + emit_thruster_particles(self->body.pos, 1.0f - progress); + } + break; + } + + case SC_LANDED: { + self->body.pos = sd->target_pos; + /* Stays landed until spacecraft_takeoff() is called externally + * (e.g. by level.c after spawning the player). */ + break; + } + + case SC_TAKEOFF: { + float progress = sd->state_timer / SC_TAKEOFF_DURATION; + if (progress >= 1.0f) { + sd->state = SC_FLYING_OUT; + sd->state_timer = 0.0f; + engine_start(sd); + } else { + float height_offset = progress * 30.0f; + self->body.pos = vec2( + sd->target_pos.x, + sd->target_pos.y - height_offset + ); + emit_thruster_particles(self->body.pos, progress); + } + break; + } + + case SC_FLYING_OUT: { + /* Fly upward and to the right, off-screen */ + float speed = sd->fly_speed * 1.5f; + self->body.pos.x += speed * 0.7f * dt; + self->body.pos.y -= speed * dt; + + emit_thruster_particles(self->body.pos, 1.0f); + + /* Check if far enough off-screen */ + if (self->body.pos.y < -SC_OFFSCREEN_DIST || + self->body.pos.x > sd->target_pos.x + SC_OFFSCREEN_DIST * 2.0f) { + engine_stop(sd); + sd->state = SC_DONE; + sd->state_timer = 0.0f; + } + break; + } + + case SC_DONE: + /* Remove the entity */ + entity_destroy(s_em, self); + return; + } +} + +static void spacecraft_render(Entity *self, const Camera *cam) { + (void)cam; + SpacecraftData *sd = (SpacecraftData *)self->data; + if (!sd || !s_texture) return; + + /* Determine which frame to show */ + int frame = SC_FRAME_LANDED; + switch (sd->state) { + case SC_FLYING_IN: + frame = SC_FRAME_LAND1; /* thrusters on while flying */ + break; + case SC_LANDING: { + float progress = sd->state_timer / SC_LAND_DURATION; + if (progress > 1.0f) progress = 1.0f; + frame = landing_frame(progress); + break; + } + case SC_LANDED: + frame = SC_FRAME_LANDED; + break; + case SC_TAKEOFF: { + float progress = sd->state_timer / SC_TAKEOFF_DURATION; + if (progress > 1.0f) progress = 1.0f; + frame = takeoff_frame(progress); + break; + } + case SC_FLYING_OUT: + frame = SC_FRAME_LAND1; + break; + case SC_DONE: + return; + } + + SDL_Rect src = frame_rect(frame); + + Sprite spr = { + .texture = s_texture, + .src = src, + .pos = self->body.pos, + .size = vec2(SPACECRAFT_WIDTH, SPACECRAFT_HEIGHT), + .flip_x = false, + .flip_y = false, + .layer = LAYER_ENTITIES, + .alpha = 255, + .rotation = 0.0, + }; + renderer_submit(&spr); +} + +static void spacecraft_destroy(Entity *self) { + if (self->data) { + SpacecraftData *sd = (SpacecraftData *)self->data; + engine_stop(sd); + free(self->data); + self->data = NULL; + } +} + +/* ═══════════════════════════════════════════════════ + * Public API + * ═══════════════════════════════════════════════════ */ + +void spacecraft_register(EntityManager *em) { + entity_register(em, ENT_SPACECRAFT, + spacecraft_update, spacecraft_render, spacecraft_destroy); + s_em = em; + load_texture(); + load_sfx(); +} + +Entity *spacecraft_spawn(EntityManager *em, Vec2 land_pos) { + load_texture(); + + /* land_pos is the top-left of where the ship should rest on the + * ground (as provided by the ENTITY directive in .lvl files). */ + Vec2 target = land_pos; + + /* Start off-screen: above and to the right */ + Vec2 start = vec2( + target.x + SC_OFFSCREEN_DIST, + target.y - SC_OFFSCREEN_DIST + ); + + Entity *e = entity_spawn(em, ENT_SPACECRAFT, start); + if (!e) return NULL; + + e->body.size = vec2(SPACECRAFT_WIDTH, SPACECRAFT_HEIGHT); + e->body.gravity_scale = 0.0f; /* manual movement, no physics */ + e->health = 999; + e->max_health = 999; + e->damage = 0; + e->flags |= ENTITY_INVINCIBLE; + + SpacecraftData *sd = calloc(1, sizeof(SpacecraftData)); + if (!sd) { + entity_destroy(em, e); + return NULL; + } + sd->state = SC_FLYING_IN; + sd->state_timer = 0.0f; + sd->target_pos = target; + sd->start_pos = start; + sd->fly_speed = SC_FLY_SPEED; + sd->is_exit_ship = false; + sd->engine_channel = -1; + e->data = sd; + + /* Start engine sound for fly-in */ + engine_start(sd); + + printf("Spacecraft spawned (fly-in to %.0f, %.0f)\n", + target.x, target.y); + return e; +} + +void spacecraft_takeoff(Entity *ship) { + if (!ship || !ship->data) return; + SpacecraftData *sd = (SpacecraftData *)ship->data; + if (sd->state == SC_LANDED) { + sd->state = SC_TAKEOFF; + sd->state_timer = 0.0f; + } +} + +bool spacecraft_is_done(const Entity *ship) { + if (!ship || !ship->data) return true; + const SpacecraftData *sd = (const SpacecraftData *)ship->data; + return sd->state == SC_DONE; +} + +bool spacecraft_is_landed(const Entity *ship) { + if (!ship || !ship->data) return false; + const SpacecraftData *sd = (const SpacecraftData *)ship->data; + return sd->state == SC_LANDED; +} diff --git a/src/game/spacecraft.h b/src/game/spacecraft.h new file mode 100644 index 0000000..75dc26d --- /dev/null +++ b/src/game/spacecraft.h @@ -0,0 +1,70 @@ +#ifndef JNR_SPACECRAFT_H +#define JNR_SPACECRAFT_H + +#include "engine/entity.h" +#include "engine/animation.h" + +/* ═══════════════════════════════════════════════════ + * Spacecraft + * + * The player's ship. Appears at level start (fly-in + * and landing sequence) and at exit zones (landing + * and fly-out when the player reaches the exit). + * + * Spritesheet: assets/sprites/spacecraft.png + * Layout: 5 frames at 80x48, horizontal strip + * 0 = landed/idle + * 1 = landing frame 1 (high, bright thrusters) + * 2 = landing frame 2 (mid, medium thrusters) + * 3 = landing frame 3 (low, dim thrusters) + * 4 = landing frame 4 (touchdown) + * ═══════════════════════════════════════════════════ */ + +/* Source sprite dimensions (pixels in PNG) */ +#define SPACECRAFT_SRC_W 80 +#define SPACECRAFT_SRC_H 48 + +/* Render scale — drawn larger than the source art */ +#define SPACECRAFT_SCALE 2.0f + +/* Rendered dimensions (used for hitbox/positioning) */ +#define SPACECRAFT_WIDTH ((int)(SPACECRAFT_SRC_W * SPACECRAFT_SCALE)) +#define SPACECRAFT_HEIGHT ((int)(SPACECRAFT_SRC_H * SPACECRAFT_SCALE)) + +/* State machine */ +typedef enum SpacecraftState { + SC_FLYING_IN, /* approaching from off-screen */ + SC_LANDING, /* playing landing animation */ + SC_LANDED, /* on the ground, idle */ + SC_TAKEOFF, /* playing takeoff animation (reverse landing) */ + SC_FLYING_OUT, /* departing off-screen */ + SC_DONE /* sequence complete, can be removed */ +} SpacecraftState; + +typedef struct SpacecraftData { + SpacecraftState state; + float state_timer; /* time spent in current state */ + Vec2 target_pos; /* where to land (ground level) */ + Vec2 start_pos; /* fly-in origin (off-screen) */ + float fly_speed; /* pixels/s during fly in/out */ + bool is_exit_ship; /* true = exit ship, false = intro ship */ + int engine_channel; /* SDL_mixer channel for engine loop, -1 = none */ +} SpacecraftData; + +/* Register the spacecraft entity type */ +void spacecraft_register(EntityManager *em); + +/* Spawn a spacecraft that will fly in and land at the given position. + * The position is the bottom-center of where the ship should land. */ +Entity *spacecraft_spawn(EntityManager *em, Vec2 land_pos); + +/* Trigger takeoff on an existing landed spacecraft */ +void spacecraft_takeoff(Entity *ship); + +/* Check if the spacecraft has finished its full sequence */ +bool spacecraft_is_done(const Entity *ship); + +/* Check if the spacecraft is in the landed state */ +bool spacecraft_is_landed(const Entity *ship); + +#endif /* JNR_SPACECRAFT_H */ diff --git a/tools/gen_spacecraft.py b/tools/gen_spacecraft.py new file mode 100644 index 0000000..90af1dd --- /dev/null +++ b/tools/gen_spacecraft.py @@ -0,0 +1,360 @@ +#!/usr/bin/env python3 +"""Generate spacecraft spritesheet PNG for the JNR engine. + +Output: assets/sprites/spacecraft.png +Layout: 5 frames horizontally, each 80x48 pixels = 400x48 total +Frames: landed, landing1, landing2, landing3, landing4 + +The ship faces RIGHT, uses the game's pixel art color palette. +""" + +from PIL import Image + +# ── Color palette (RGBA) ───────────────────────────── +T = (0, 0, 0, 0) # transparent +BLK = (26, 26, 46, 255) # dark/outline +WHT = (238, 238, 234, 255) # white +GRY = (136, 136, 136, 255) # grey +GYD = (85, 85, 85, 255) # grey dark +GYL = (187, 187, 187, 255) # grey light +BLU = (74, 124, 189, 255) # blue accent +BLD = (54, 94, 143, 255) # blue dark +BLL = (111, 168, 220, 255) # blue light +CYN = (77, 208, 225, 255) # cyan cockpit +CYD = (0, 172, 193, 255) # cyan dark +ORG = (255, 152, 0, 255) # orange +ORD = (204, 122, 0, 255) # orange dark +YLW = (255, 213, 79, 255) # yellow +RED = (217, 68, 68, 255) # red + +W, H = 80, 48 # frame size + + +def make_frame(): + """Return a blank 80x48 pixel grid.""" + return [[T] * W for _ in range(H)] + + +def hline(grid, y, x0, x1, color): + """Draw horizontal line from x0 to x1 inclusive.""" + if y < 0 or y >= H: + return + for x in range(max(0, x0), min(W, x1 + 1)): + grid[y][x] = color + + +def pixel(grid, x, y, color): + """Set a single pixel.""" + if 0 <= x < W and 0 <= y < H: + grid[y][x] = color + + +def draw_ship_body(grid, oy=0): + """Draw the main spacecraft hull, offset vertically by oy pixels. + + Ship anatomy (facing right): + - Engine block: cols 10-18, thick rear + - Main fuselage: cols 18-58, ~10px tall + - Cockpit: cols 58-66, with CYN canopy + - Nose: cols 66-72, pointed tip + - Upper fin: cols 22-38, extends upward + - Lower fin: cols 22-38, extends downward + """ + cy = 23 + oy # vertical center of fuselage + + # ── Main fuselage (rows cy-5 to cy+4 = 10px tall) ── + for dy in range(-5, 5): + y = cy + dy + # Fuselage hull + if dy == -5: + # Top edge — light highlight + hline(grid, y, 20, 60, GYL) + elif dy == 4: + # Bottom edge — dark shadow + hline(grid, y, 20, 60, BLK) + elif dy == -4: + hline(grid, y, 18, 62, GYL) + elif dy == 3: + hline(grid, y, 18, 62, GYD) + elif dy == -1 or dy == 0: + # Blue racing stripe through center + hline(grid, y, 16, 64, BLU) + elif dy == -2: + hline(grid, y, 17, 63, GRY) + elif dy == 1: + hline(grid, y, 17, 63, GRY) + elif dy == 2: + hline(grid, y, 18, 62, GYD) + elif dy == -3: + hline(grid, y, 19, 61, GRY) + + # ── Engine block (rear, left side) ── + for dy in range(-6, 7): + y = cy + dy + if -6 <= dy <= 6: + # Engine nacelle housing + if abs(dy) <= 3: + hline(grid, y, 10, 18, GYD) + elif abs(dy) <= 5: + hline(grid, y, 12, 18, GYD) + elif abs(dy) == 6: + hline(grid, y, 14, 17, GYD) + + # Engine inner dark (thruster openings) + for dy in range(-3, 4): + y = cy + dy + if abs(dy) <= 2: + hline(grid, y, 10, 12, BLK) + elif abs(dy) == 3: + pixel(grid, 11, y, BLK) + pixel(grid, 12, y, BLK) + + # Engine highlight on top + hline(grid, cy - 4, 13, 17, GRY) + hline(grid, cy - 5, 14, 17, GRY) + + # ── Cockpit canopy ── + # Canopy frame (CYD) and glass (CYN) + for dy in range(-4, 3): + y = cy + dy + if dy == -4: + hline(grid, y, 58, 63, CYD) + elif dy == -3: + hline(grid, y, 59, 65, CYD) + hline(grid, y, 60, 64, CYN) + elif dy == -2: + hline(grid, y, 60, 66, CYD) + hline(grid, y, 61, 65, CYN) + pixel(grid, 63, y, WHT) # glint + elif dy == -1: + hline(grid, y, 61, 67, CYD) + hline(grid, y, 62, 66, CYN) + elif dy == 0: + hline(grid, y, 61, 67, CYD) + hline(grid, y, 62, 66, CYN) + pixel(grid, 64, y, WHT) # glint + elif dy == 1: + hline(grid, y, 60, 66, CYD) + hline(grid, y, 61, 65, CYN) + elif dy == 2: + hline(grid, y, 58, 64, CYD) + + # ── Nose (pointed tip facing right) ── + for dy in range(-3, 4): + y = cy + dy + if dy == 0: + hline(grid, y, 66, 73, GYL) # center line + elif abs(dy) == 1: + hline(grid, y, 66, 71, GRY) + elif abs(dy) == 2: + hline(grid, y, 65, 69, GYD) + elif abs(dy) == 3: + hline(grid, y, 65, 67, BLK) + + # Nose tip highlight + pixel(grid, 73, cy, WHT) + pixel(grid, 72, cy, WHT) + + # ── Upper wing fin ── + for i in range(8): + y = cy - 6 - i + x0 = 26 + i + x1 = 40 - i + if x0 <= x1: + hline(grid, y, x0, x1, GYD) + # Highlight top edge + if i < 7: + pixel(grid, x0, y, GYL) + # BLU accent + if x1 - x0 > 2: + pixel(grid, (x0 + x1) // 2, y, BLU) + + # Wing fin tip accent + pixel(grid, 33, cy - 13, BLU) + + # ── Lower wing fin ── + for i in range(8): + y = cy + 5 + i + x0 = 26 + i + x1 = 40 - i + if x0 <= x1: + hline(grid, y, x0, x1, GYD) + # Shadow bottom edge + if i < 7: + pixel(grid, x0, y, BLK) + # BLU accent + if x1 - x0 > 2: + pixel(grid, (x0 + x1) // 2, y, BLD) + + # ── Panel detail lines ── + for dy in [-3, 2]: + y = cy + dy + for x in [30, 42, 52]: + pixel(grid, x, y, GYD if dy < 0 else BLK) + + +def draw_landing_gear(grid, oy=0, length=4): + """Draw two landing gear struts below the hull.""" + cy = 23 + oy + gear_top = cy + 5 # just below hull bottom + + # Front gear (col 52) + for dy in range(length): + y = gear_top + dy + pixel(grid, 52, y, GYD) + pixel(grid, 53, y, GYD) + # Foot + hline(grid, gear_top + length, 51, 54, BLK) + + # Rear gear (col 26) + for dy in range(length): + y = gear_top + dy + pixel(grid, 26, y, GYD) + pixel(grid, 27, y, GYD) + # Foot + hline(grid, gear_top + length, 25, 28, BLK) + + +def draw_thrusters(grid, oy=0, intensity=0): + """Draw thruster flames behind the engine. + + intensity: 0=off, 1=dim, 2=medium, 3=bright + """ + if intensity == 0: + return + + cy = 23 + oy + + if intensity >= 3: + # Bright: long flame with white core + for dy in range(-2, 3): + y = cy + dy + if abs(dy) == 0: + hline(grid, y, 1, 9, YLW) + hline(grid, y, 3, 7, WHT) + elif abs(dy) == 1: + hline(grid, y, 3, 9, ORG) + hline(grid, y, 4, 8, YLW) + elif abs(dy) == 2: + hline(grid, y, 5, 9, ORD) + # Flame tip flicker + pixel(grid, 1, cy, WHT) + pixel(grid, 2, cy, YLW) + elif intensity >= 2: + # Medium flame + for dy in range(-1, 2): + y = cy + dy + if dy == 0: + hline(grid, y, 4, 9, YLW) + hline(grid, y, 5, 8, WHT) + else: + hline(grid, y, 5, 9, ORG) + hline(grid, y, 6, 8, YLW) + pixel(grid, 4, cy - 2, ORD) + pixel(grid, 4, cy + 2, ORD) + elif intensity >= 1: + # Dim glow + for dy in range(-1, 2): + y = cy + dy + if dy == 0: + hline(grid, y, 7, 9, ORD) + pixel(grid, 8, y, ORG) + else: + pixel(grid, 8, y, ORD) + pixel(grid, 9, y, ORD) + + +def apply_tilt(grid, tilt_pixels): + """Shift the left side of the image down by tilt_pixels to simulate + nose-up tilt. This is a crude per-column vertical shift.""" + if tilt_pixels == 0: + return grid + + new_grid = make_frame() + for x in range(W): + # Linear interpolation: left edge shifts down by tilt_pixels, + # right edge stays the same + shift = int(tilt_pixels * (1.0 - x / W)) + for y in range(H): + src_y = y - shift + if 0 <= src_y < H: + new_grid[y][x] = grid[src_y][x] + return new_grid + + +def make_landed(): + """Frame: ship landed on ground, gear down, no thrust.""" + grid = make_frame() + draw_ship_body(grid, oy=0) + draw_landing_gear(grid, oy=0, length=5) + return grid + + +def make_landing1(): + """Frame 1: ship high, tilted nose-up, bright thrusters.""" + grid = make_frame() + draw_ship_body(grid, oy=-8) + draw_thrusters(grid, oy=-8, intensity=3) + grid = apply_tilt(grid, 3) + return grid + + +def make_landing2(): + """Frame 2: ship lower, less tilt, medium thrusters.""" + grid = make_frame() + draw_ship_body(grid, oy=-4) + draw_thrusters(grid, oy=-4, intensity=2) + grid = apply_tilt(grid, 1) + return grid + + +def make_landing3(): + """Frame 3: ship nearly level, near ground, gear extending, dim thrusters.""" + grid = make_frame() + draw_ship_body(grid, oy=-2) + draw_landing_gear(grid, oy=-2, length=3) + draw_thrusters(grid, oy=-2, intensity=1) + return grid + + +def make_landing4(): + """Frame 4: ship touching down, gear extended, no thrust.""" + grid = make_frame() + draw_ship_body(grid, oy=0) + draw_landing_gear(grid, oy=0, length=5) + # Nearly identical to landed — maybe a very slight glow + pixel(grid, 9, 23, ORD) # residual engine heat + return grid + + +def grid_to_image(grid): + """Convert a pixel grid to a PIL Image.""" + img = Image.new("RGBA", (W, H), (0, 0, 0, 0)) + for y in range(H): + for x in range(W): + img.putpixel((x, y), grid[y][x]) + return img + + +def main(): + frames = [ + make_landed(), + make_landing1(), + make_landing2(), + make_landing3(), + make_landing4(), + ] + + # Combine into horizontal spritesheet + sheet = Image.new("RGBA", (W * len(frames), H), (0, 0, 0, 0)) + for i, frame in enumerate(frames): + img = grid_to_image(frame) + sheet.paste(img, (i * W, 0)) + + out_path = "assets/sprites/spacecraft.png" + sheet.save(out_path) + print(f"Saved spritesheet: {out_path} ({sheet.width}x{sheet.height})") + + +if __name__ == "__main__": + main()