From 364a34ce33ac8c36d68dc1e7009d4fbfa6ffd7fc Mon Sep 17 00:00:00 2001 From: Trevor Boddy Date: Mon, 16 Feb 2026 17:00:35 -0500 Subject: [PATCH] shit! --- res/fontbig.png | Bin 0 -> 2143 bytes res/fontbigshadow.png | Bin 0 -> 2143 bytes res/ground.png | Bin 3110 -> 5648 bytes res/human.png | Bin 0 -> 4106 bytes res/life.png | Bin 0 -> 1889 bytes res/life2.png | Bin 0 -> 1917 bytes res/mapindicator.png | Bin 1899 -> 1907 bytes res/resources.res | 8 ++- res/sky.png | Bin 5152 -> 5152 bytes src/background.h | 6 +-- src/bullets.h | 19 +++++-- src/chrome.h | 117 +++++++++++++++++++++++++++++++++++++++--- src/enemies.h | 24 +++++++-- src/enemytypes.h | 86 ++++++++++++++++++++++++------- src/global.h | 67 +++++++++++++++++++++++- src/humans.h | 96 ++++++++++++++++++++++++++++++++++ src/main.c | 3 ++ src/player.h | 38 ++++++++------ src/sfx.h | 45 ++++++++++++---- src/stage.h | 8 ++- 20 files changed, 453 insertions(+), 64 deletions(-) create mode 100644 res/fontbig.png create mode 100644 res/fontbigshadow.png create mode 100644 res/human.png create mode 100644 res/life.png create mode 100644 res/life2.png create mode 100644 src/humans.h diff --git a/res/fontbig.png b/res/fontbig.png new file mode 100644 index 0000000000000000000000000000000000000000..d394a915104407913ce5c2fddef899b26cf0d41f GIT binary patch literal 2143 zcmbVNYitx%6dq_;DvyXkh=m`H10qG{xwF&Vk!{6pp$lxO?M6$~)Vp`??oPTpGt5l) z0a%+Fz#0LyQGUFLib9l!kcbh2KPYGl(TY|ikszQ36-2;0+uaQ%fDN0>-aB`` z`uT{j+!)zAc(QS+CT#y+wn85v;_a3S+R0A9=Q4i;Tk-ajhjAaTg`^v zNo4+O~PupBK*#e>CBc-DA`)vIt`I zc;^{LytD2Bf^c=I;U>E&v_OV>k^zbyK};rTU^GF@s>&Duwji5~pqQ%psSA7dQ>3c+ zsi!?5E@V`rxLUi!M2$=8!f;6o^eI$TB{?f2V}c~I0hviAG)vC-sT{A2=gu-qkvWLn z;-_Xi2Fa#SJz1@rh~znjqanv}WQC7WI1xf#@_A>H0w;J_u7c$Rnipi=EAwtL|D&)) zQ;EtAfjN15_{~qnZQGDpHl0p0X*Z*rF_!oFe3*b01R5h~E34TcLu=NQ0z&{<&{Pdu z)iuQET}_EXr~0U1f7NUK@-P~o7l8DOwH!#T2YKm`_YMyr{~8CM{S5-5o@+rn7B zh&AH6ty^*ZHq^z>gACyChC)RfxAY~MESj+FniQ6i56CUiRyb=QwgFjss|is}3cESQ zQDex}CIYr@hIKtrNLGEpWm1qBft(%!P}Lj}Za#c$$;K0w)Wi?7?%7 z%y9$IkgllFY!NEaPJX2@FUoG8EQ*7mxNsC;gZ~99P>$+m5@5+{62uT|XfcW`22!rp z6S|2FJKp8I2!-UJX4yc4C>ZclST&=niY!Tj$RPpHB1CRljC!MV1d5Q300HqL6eG~< zE$k2Iu+_&z7y+yw;Q@DPG)Wiw`ylvyeIHW@uooIsL}YwWHGA zOmolPUKjh%F;~Zksl7dIggLLW<;|Z{ZJWO+E8jJ(1)k{Iw0`-fbuF)?<@Pc6Uj?kI za$_94_EtmX_L7&cxK}@Q-ycVRo4)Vrk#k)ml#@T8Qy-?TT<@IP{lew`;kt3!x73`l z=Gu`<&4-82+~Zll%C)-3J7eMY72##SAN^o*)iXrZmj};2H*);tiI0W*cDy^cZQefO Yr$aj*Jpb^gF6SB#*3<>IKJk3Vf4xZC5&!@I literal 0 HcmV?d00001 diff --git a/res/fontbigshadow.png b/res/fontbigshadow.png new file mode 100644 index 0000000000000000000000000000000000000000..56ae8d0afdcbc456b0e6ad5ec5fbdaa460165767 GIT binary patch literal 2143 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 SRB#l4|IpPm(7>tLzxzMuC48#@ delta 1480 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 literal 0 HcmV?d00001 diff --git a/res/life.png b/res/life.png new file mode 100644 index 0000000000000000000000000000000000000000..6a7c981125387eefd97888f617b0eeef9574ab7e GIT binary patch literal 1889 zcmbVNO>Epm6gH(=LaT_PN{CjGFph*;f!E`Id(>?S*)3TmPNT#U;Y7yc*4i?h)WR%4#2hWygybOB_+yA-WiX-_r3SM z_h#n3wbhqrPn53_Pc19)d%jzcJ)SJRnsMC8)5Y@$ z_tCYdIj-_y;BMxd&I+b+SS5b!vFad90L^h1E)5bww^`16tQ|x(;m@zW7Wlxg39sl5 za*{>X36^$Kwz0eF(%o%p`@*G*{DlDqf{^8eAB5c~!-JYI;>9p8mL-87LGtaIuuwSU zH=Q+pF-{pTqbd?9LWno*s*f~EWy7{!;1#6k5;7&Eh_ZrZ3(G1$`3c}L^;>woetF^# z-fBW8&l4<3{eHjNSF3T_mSo$ufk09e5fEZFjB+v%qilZ4P-huUgCq~)h%Xq47w_aX z0iqqt2$PaF$|k9Tq)7vkNOBbwvPM9kmbhdm?T(!LRAOBgvMA30E0@@$6X$W(i4UMI zKR@6A((5>-k9}ha!_q~TH+mprl8}ATnLA9Fw9c}4C#9^>gD~d{H3?o!8Oh_+jpOdL zSZmXedBv!z{5glvASy(hzt4o#NzQ6QQ8y8ahAdZBu?BUsMWkbd#!x5rgVwNw8U<7~ zTv@{!!kYR36dK1TIr(3(PjM?wLjsb6khGbUL~VgD6Nwk&Zk&SSBDzr&j)R*~mXnCG zX1ykW>S_@97!k{EQQr_v)sRKauuRdSiYDp`vzh6swr6@%`+7`wirP-?eXzGI>H!{! znx)&Krr5qnv{p+rET+h|PIO<^r}kIU0QMK@9+{`ubwyL)r67ZH4JUhIgS|ePbpw9X z2bj>JziR?53d?+9vK;J_fx{^n)AlM9q|;e5Rp_EkiMpMJ>;_ApQFVFRQMiU}R$r`f=Q=M+<(!PCF!OGdMIQ z;plP>*NuDR9JHSfC&%2oxyNREqR2S3oo`9F~reP6e zDusLG$1@)kID8Rl0TS_}m=--L2!lsp*S%pGlrvshphua^|nsp8Dds%BTNs p-Fvgrs(kR%6YpNVhGx;(V_e*OO#g=4+AJ=zW@EMf`Ab`G{Rf#VO`QM$ literal 0 HcmV?d00001 diff --git a/res/life2.png b/res/life2.png new file mode 100644 index 0000000000000000000000000000000000000000..2b01299bf5dad774f6bac01ef3ed2a9e33aef242 GIT binary patch literal 1917 zcmbVNONbmr7#p zx-0kI`;X*u_ieb1>4$`_@I-QW%k@k0AD?_GOD-$P z&lKyh9+r9Aom-3e(%O83uB}j;$!AVWr}|hBcswCe-|P4>?w8~tFBbRdYeklZkYuGK z&!hpR<@$nD4k9igSb%^+2qn`lFsM_C4BI*>sZcE{&{UubkcyFoktU6QvWOV5CSI(} zj^l}^lH5*`5GzWr*DLh2LJ+kSWZSkNP*fEN1c(QILi)guk4+dVJf@KwCT`$MDI;-$ z)ubc~wWBn=Fr)S3aj8Vnls*X+RDfw(Lm;CWE?kW|!{Cf6yu&^2C$Yey3>&tCB#7I= zKGfOg9RWmn>-8+go?1LF3lS&Pu1I5CkUi0HV-Rv>k;lPmM0vFALB{4bbM+zcX*h?HHAw73%bEm_J6iOWGJh(zF2-B3lnj%$9L z5TEi|r6h~23$Dwssu_mbv<+aYrVez=;lP620J>=yhNe-I+16x!C7`QmZ`1iBtFwR# zj{l;nNK;MZPyii^fTpTDz@ktGDr1mx8(AhL6Sj+yD^?rnY-deXF~MkA)S;>lg`lDh zbk$~n=*=cDEUqHENQw*a_*gpAD4!CX9O=x`*>80n zEFE+AQXfs*m5!3`nkS$mBz8krB%KY}gqX7;HRU978lF zCMP7cI2AD}xBilx;ParVYlBm!UD6g~SEX`2YBq`NI*TT}+KsPnb zz$neaz}Ujrz;g3O<_adFR8Bs~BF<$NVrXIov1>kS$mBz8kpX5=IYJB!3?`l~jv*Qo zlM@nJoQfEgTYpJT@OjYGwPDhtxidf7vltl^7;gH<_BuW3m%F>TL4Yw2o0~nu(K@z( TnV!V~K>ZA!u6{1-oD!M 2 ? 64 : 0)), x * 8, y * 8, 8, 8); } } // for(u8 x = 0; x < 5; x++){ @@ -22,5 +22,5 @@ void loadBackground(){ void updateBackground(){ VDP_setHorizontalScroll(BG_B, fix32ToInt(-player.camera)); - VDP_setVerticalScroll(BG_B, (fix32ToInt(player.pos.y) - BG_OFF) >> 2); + VDP_setVerticalScroll(BG_B, (fix32ToInt(player.pos.y) - BG_OFF) >> 3); } \ No newline at end of file diff --git a/src/bullets.h b/src/bullets.h index 5c34ff1..4540bef 100644 --- a/src/bullets.h +++ b/src/bullets.h @@ -57,6 +57,7 @@ static void doBulletRotation(u8 i){ } void spawnBullet(struct bulletSpawner spawner, void(*updater)){ + if(player.recoveringClock > 0 && !spawner.player) return; // Don't spawn if offscreen fix32 dx = getWrappedDelta(spawner.x, player.pos.x); bool offScreenX = (dx < -CULL_LIMIT || dx > CULL_LIMIT); @@ -124,6 +125,7 @@ static void collideWithEnemy(u8 i){ deltaX >= -BULLET_CHECK && deltaX <= BULLET_CHECK){ bulletDist = getApproximatedDistance(fix32ToInt(deltaX), fix32ToInt(deltaY)); if(bulletDist <= bullets[i].dist){ + score += (enemies[j].ints[3] >= 0) ? 200 : 100; killBullet(i, TRUE); killEnemy(j); sfxExplosion(); @@ -134,6 +136,7 @@ static void collideWithEnemy(u8 i){ } static void collideWithPlayer(u8 i){ + if(player.recoveringClock > 0) return; fix32 deltaX = getWrappedDelta(bullets[i].pos.x, player.pos.x); fix32 deltaY = bullets[i].pos.y - player.pos.y; @@ -143,8 +146,13 @@ static void collideWithPlayer(u8 i){ if(dist <= 4){ killBullet(i, TRUE); sfxExplosion(); - // player.lives--; - // if(player.lives <= 0) gameOver = TRUE; + player.lives--; + if(player.lives == 0){ + gameOver = TRUE; + } else { + player.recoveringClock = 120; + killBullets = TRUE; + } } } @@ -195,7 +203,7 @@ static void updateBullet(u8 i){ } if(bullets[i].clock > 0) bullets[i].updater(i); if(bullets[i].player) collideWithEnemy(i); - else collideWithPlayer(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); @@ -209,6 +217,11 @@ static void updateBullet(u8 i){ void updateBullets(){ bulletCount = 0; + if(killBullets){ + killBullets = FALSE; + for(s16 i = 0; i < BULLET_COUNT; i++) + if(bullets[i].active && !bullets[i].player) killBullet(i, TRUE); + } for(s16 i = 0; i < BULLET_COUNT; i++) if(bullets[i].active) updateBullet(i); // intToStr(bulletCount, debugStr, 1); diff --git a/src/chrome.h b/src/chrome.h index 1aa8d6e..a82bc22 100644 --- a/src/chrome.h +++ b/src/chrome.h @@ -2,18 +2,55 @@ #define MAP_TILE TILE_ATTR_FULL(PAL1, 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_BORDER_X_TILE TILE_ATTR_FULL(PAL0, 1, 0, 0, MAP_I + 3) +#define MAP_HUMAN_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 FONT_BIG_I 256 + +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); + } + } +} char scoreStr[SCORE_LENGTH]; u32 lastScore; +s16 scoreLength; + +#define SCORE_X 1 +#define SCORE_Y 5 + +#define LIFE_I (FONT_BIG_I + 64) +#define LIVES_X 38 +#define LIVES_Y 5 +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); + lastLives = player.lives; +} // previous map positions: -1 means not drawn s16 mapEnemyCol[ENEMY_COUNT], mapEnemyRow[ENEMY_COUNT]; +s16 mapHumanCol[HUMAN_COUNT], mapHumanRow[HUMAN_COUNT]; s16 mapPlayerRow; static void drawScore(){ - uintToStr(score, scoreStr, 1); - VDP_drawText(scoreStr, 1, 5); + if(lastScore < 10) scoreLength = 1; + else if(lastScore < 100) scoreLength = 2; + else if(lastScore < 1000) scoreLength = 3; + else if(lastScore < 10000) scoreLength = 4; + else if(lastScore < 100000) scoreLength = 5; + else if(lastScore < 1000000) scoreLength = 6; + else if(lastScore < 10000000) scoreLength = 7; + else scoreLength = 8; + uintToStr(lastScore, scoreStr, scoreLength); + bigText(scoreStr, SCORE_X, SCORE_Y, FALSE); } // load map when stage does so we have all enemies + player @@ -29,17 +66,24 @@ void loadMap(){ mapEnemyCol[i] = -1; mapEnemyRow[i] = -1; } + for(s16 i = 0; i < HUMAN_COUNT; i++){ + mapHumanCol[i] = -1; + mapHumanRow[i] = -1; + } mapPlayerRow = -1; } // temp arrays for new positions s16 mapNewCol[ENEMY_COUNT], mapNewRow[ENEMY_COUNT]; +s16 mapNewHumanCol[HUMAN_COUNT], mapNewHumanRow[HUMAN_COUNT]; static bool mapTileOccupied(s16 col, s16 row, s16 pRow){ // player always at center column if(col == MAP_W / 2 && row == pRow) return TRUE; for(s16 i = 0; i < ENEMY_COUNT; i++) if(mapNewCol[i] == col && mapNewRow[i] == row) return TRUE; + for(s16 i = 0; i < HUMAN_COUNT; i++) + if(mapNewHumanCol[i] == col && mapNewHumanRow[i] == row) return TRUE; return FALSE; } @@ -67,6 +111,24 @@ static void updateMap(){ mapNewRow[i] = row; } + // compute new human positions + for(s16 i = 0; i < HUMAN_COUNT; i++){ + if(!humans[i].active || humans[i].image == NULL){ + mapNewHumanCol[i] = -1; + mapNewHumanRow[i] = -1; + continue; + } + fix32 dx = getWrappedDelta(humans[i].pos.x, player.pos.x); + s16 col = fix32ToInt(dx) / 54 + MAP_W / 2; + if(col < 0) col = 0; + if(col >= MAP_W) col = MAP_W - 1; + s16 row = fix32ToInt(humans[i].pos.y) / 75; + if(row < 0) row = 0; + if(row >= MAP_H) row = MAP_H - 1; + mapNewHumanCol[i] = col; + mapNewHumanRow[i] = row; + } + // clear old player tile if it moved and nothing new occupies it if(mapPlayerRow >= 0 && mapPlayerRow != pRow) if(!mapTileOccupied(MAP_W / 2, mapPlayerRow, pRow)) @@ -80,6 +142,23 @@ static void updateMap(){ VDP_setTileMapXY(BG_A, MAP_TILE, MAP_X + mapEnemyCol[i], MAP_Y + mapEnemyRow[i]); } + // clear old human tiles that moved or disappeared + for(s16 i = 0; i < HUMAN_COUNT; i++){ + if(mapHumanCol[i] < 0) continue; + if(mapHumanCol[i] == mapNewHumanCol[i] && mapHumanRow[i] == mapNewHumanRow[i]) continue; + if(!mapTileOccupied(mapHumanCol[i], mapHumanRow[i], pRow)) + VDP_setTileMapXY(BG_A, MAP_TILE, MAP_X + mapHumanCol[i], MAP_Y + mapHumanRow[i]); + } + + // draw human dots (skip if player occupies same tile) + for(s16 i = 0; i < HUMAN_COUNT; i++){ + mapHumanCol[i] = mapNewHumanCol[i]; + mapHumanRow[i] = mapNewHumanRow[i]; + if(mapNewHumanCol[i] < 0) continue; + if(mapNewHumanCol[i] == MAP_W / 2 && mapNewHumanRow[i] == pRow) continue; + VDP_setTileMapXY(BG_A, MAP_HUMAN_TILE, MAP_X + mapNewHumanCol[i], MAP_Y + mapNewHumanRow[i]); + } + // draw enemy dots (skip if player occupies same tile) for(s16 i = 0; i < ENEMY_COUNT; i++){ mapEnemyCol[i] = mapNewCol[i]; @@ -95,18 +174,42 @@ static void updateMap(){ } void loadChrome(){ + VDP_loadTileSet(imageFontBig.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(); +} + +bool didGameOver; +static void doGameOver(){ + didGameOver = TRUE; + 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 < HUMAN_COUNT; i++) if(humans[i].active) SPR_setPalette(humans[i].image, PAL1); + SPR_releaseSprite(player.image); + // clear minimap + VDP_clearTileMapRect(BG_A, MAP_X, MAP_Y, MAP_W, MAP_H); + // clear score + VDP_clearTileMapRect(BG_A, SCORE_X, SCORE_Y, SCORE_LENGTH, 2); + + // clear lives + VDP_clearTileMapRect(BG_A, LIVES_X, LIVES_Y, 1, 16); + + VDP_drawText("GAME OVER", 15, 13); + VDP_drawText("PRESS ANY BUTTON", 12, 14); } void updateChrome(){ - score++; - if(score > 99999999) score = 0; + if(gameOver && !didGameOver) doGameOver(); + if(didGameOver) return; if(lastScore != score){ lastScore = score; drawScore(); } + if(lastLives != player.lives) drawLives(); if(clock % 4 == 0) updateMap(); - // VDP_clearText(1, 26, 4); - // VDP_drawText(debugStr, 1, 26); } \ No newline at end of file diff --git a/src/enemies.h b/src/enemies.h index 381a349..1bc4039 100644 --- a/src/enemies.h +++ b/src/enemies.h @@ -34,7 +34,7 @@ void spawnEnemy(u8 type, u8 zone){ do { // Random X within zone: zoneStart + random(0-511) randX = zoneStart + FIX32(random() % 512); - randY = FIX32(16 + (random() % 192)); + randY = FIX32(16 + (random() % 128)); attempts++; } while(!isValidEnemyPosition(randX, randY) && attempts < 100); @@ -61,8 +61,22 @@ void spawnEnemy(u8 type, u8 zone){ } static void boundsEnemy(u8 i){ - 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].ints[3] >= 0){ + // carrying: only check for reaching the top + if(enemies[i].pos.y <= FIX32(0)){ + s16 h = enemies[i].ints[3]; + if(humans[h].active) killHuman(h); + enemies[i].ints[3] = -1; + humanBeingCarried = FALSE; + // TODO: spawn mutant here + 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.x >= GAME_WRAP){ enemies[i].pos.x -= GAME_WRAP; @@ -73,7 +87,6 @@ static void boundsEnemy(u8 i){ } static void updateEnemy(u8 i){ - boundsEnemy(i); enemies[i].pos.x += enemies[i].vel.x; enemies[i].pos.y += enemies[i].vel.y; @@ -83,6 +96,9 @@ static void updateEnemy(u8 i){ break; } + boundsEnemy(i); + if(!enemies[i].active) return; + s16 sx = getScreenX(enemies[i].pos.x, player.camera); s16 sy = fix32ToInt(enemies[i].pos.y); fix32 dx = getWrappedDelta(enemies[i].pos.x, player.pos.x); diff --git a/src/enemytypes.h b/src/enemytypes.h index f538b02..7d83041 100644 --- a/src/enemytypes.h +++ b/src/enemytypes.h @@ -1,18 +1,81 @@ void loadEnemyOne(u8 i){ - enemies[i].ints[0] = random() % 40; + enemies[i].ints[0] = random() % 60; + enemies[i].ints[2] = -1; // target human index + enemies[i].ints[3] = -1; // carried human index enemies[i].angle = ((random() % 4) * 256) + 128; enemies[i].speed = FIX32(2); } void updateEnemyOne(u8 i){ - if(enemies[i].clock % 40 == enemies[i].ints[0] && enemies[i].onScreen){ - enemies[i].clock % 80 == enemies[i].ints[0] ? sfxEnemyShotB() : sfxEnemyShotA(); + // 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); + return; + } + + // cancel any target if a human is already being carried + if(humanBeingCarried && enemies[i].ints[2] >= 0){ + enemies[i].ints[2] = -1; + } + + // seeking behavior: periodically look for a human to grab + if(!humanBeingCarried && enemies[i].clock % 30 == (u32)(enemies[i].ints[0]) % 30){ + s16 bestHuman = -1; + fix32 bestDist = FIX32(9999); + for(s16 j = 0; j < HUMAN_COUNT; j++){ + if(!humans[j].active || humans[j].state != HUMAN_WALKING) continue; + fix32 dx = getWrappedDelta(enemies[i].pos.x, humans[j].pos.x); + fix32 dy = enemies[i].pos.y - humans[j].pos.y; + fix32 dist = (dx < 0 ? -dx : dx) + (dy < 0 ? -dy : dy); + if(dist < bestDist && dist < FIX32(256)){ + bestDist = dist; + bestHuman = j; + } + } + enemies[i].ints[2] = bestHuman; + } + + // steer toward target human + if(enemies[i].ints[2] >= 0){ + s16 t = enemies[i].ints[2]; + if(!humans[t].active || humans[t].state != HUMAN_WALKING){ + enemies[i].ints[2] = -1; + } else { + fix32 dx = getWrappedDelta(humans[t].pos.x, enemies[i].pos.x); + fix32 dy = humans[t].pos.y - enemies[i].pos.y; + + // hone toward human's current position at base speed + s16 angle = honeAngle( + fix32ToFix16(enemies[i].pos.x), fix32ToFix16(humans[t].pos.x), + fix32ToFix16(enemies[i].pos.y), fix32ToFix16(humans[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); + + // grab check: within 16px + fix32 adx = dx < 0 ? -dx : dx; + fix32 ady = dy < 0 ? -dy : dy; + if(adx < FIX32(16) && ady < FIX32(16)){ + enemies[i].ints[3] = t; + enemies[i].ints[2] = -1; + humanBeingCarried = TRUE; + humans[t].state = HUMAN_CARRIED; + humans[t].carriedBy = i; + return; + } + } + } + + // normal shooting + if(enemies[i].clock % 60 == enemies[i].ints[0] && enemies[i].onScreen){ + enemies[i].clock % 120 == enemies[i].ints[0] ? sfxEnemyShotB() : sfxEnemyShotA(); struct bulletSpawner spawner = { .x = enemies[i].pos.x, .y = enemies[i].pos.y, .anim = 3 + (random() % 3), - // .anim = 6, - // .frame = 1, .speed = FIX32(2) + FIX16(random() % 4), .angle = random() % 128, }; @@ -26,16 +89,3 @@ void updateEnemyOne(u8 i){ } } } - -void updateLander(u8 i){} - -void updateMutant(u8 i){} - -void updateSwarmer(u8 i){} - -void updatePod(u8 i){} - -void updateBomber(u8 i){} - -void updateBaiter(u8 i){} - diff --git a/src/global.h b/src/global.h index 77b84cc..ba83d82 100644 --- a/src/global.h +++ b/src/global.h @@ -50,15 +50,16 @@ void updateControls(u16 joy, u16 changed, u16 state){ struct playerStruct { Vect2D_f32 pos, vel; s16 shotAngle; - u8 lives; + u8 lives, recoveringClock; fix32 camera, yCamera; Sprite* image; }; struct playerStruct player; +bool killBullets; // bullets -#define BULLET_COUNT 64 +#define BULLET_COUNT 70 struct bulletSpawner { fix32 x, y, speed; @@ -92,6 +93,31 @@ struct enemy { }; struct enemy enemies[ENEMY_COUNT]; +// humans +#define HUMAN_COUNT 8 +#define HUMAN_WALKING 0 +#define HUMAN_CARRIED 1 +#define HUMAN_FALLING 2 + +struct human { + bool active; + u8 state; + s16 carriedBy; + Vect2D_f32 pos, vel; + Sprite* image; +}; +struct human humans[HUMAN_COUNT]; +bool humanBeingCarried; + +void killHuman(u8 i){ + if(humans[i].state == HUMAN_CARRIED && humans[i].carriedBy >= 0){ + enemies[humans[i].carriedBy].ints[3] = -1; + humanBeingCarried = FALSE; + } + humans[i].active = FALSE; + SPR_releaseSprite(humans[i].image); +} + void killBullet(u8 i, bool explode){ if(explode){ if(bullets[i].player){ @@ -120,6 +146,16 @@ void killBullet(u8 i, bool explode){ } void killEnemy(u8 i){ + if(enemies[i].ints[3] >= 0){ + s16 h = enemies[i].ints[3]; + if(humans[h].active){ + humans[h].state = HUMAN_FALLING; + humans[h].carriedBy = -1; + humans[h].vel.x = 0; + humans[h].vel.y = FIX32(2); + } + humanBeingCarried = FALSE; + } enemies[i].active = FALSE; SPR_releaseSprite(enemies[i].image); } @@ -142,4 +178,31 @@ static s16 getScreenX(fix32 worldX, fix32 camera) { screenX -= GAME_WRAP; } return fix32ToInt(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)))); +} +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)))); +} +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; + } + return arcAngle; } \ No newline at end of file diff --git a/src/humans.h b/src/humans.h new file mode 100644 index 0000000..633bb84 --- /dev/null +++ b/src/humans.h @@ -0,0 +1,96 @@ + +void spawnHuman(u8 zone){ + s16 i = -1; + for(s16 j = 0; j < HUMAN_COUNT; j++) if(!humans[j].active) { i = j; break; } + if(i == -1) return; + + humans[i].active = TRUE; + humans[i].state = HUMAN_WALKING; + humans[i].carriedBy = -1; + + fix32 zoneStart = FIX32(zone * 512); + humans[i].pos.x = zoneStart + FIX32(random() % 512); + humans[i].pos.y = GAME_H_F - FIX32(24); + + fix32 speeds[] = { FIX32(0.3), FIX32(0.4), FIX32(0.5) }; + humans[i].vel.x = (random() % 2 == 0) ? speeds[random() % 3] : -speeds[random() % 3]; + humans[i].vel.y = (random() % 2 == 0) ? FIX32(0.1) : FIX32(-0.1); + + humans[i].image = SPR_addSprite(&humanSprite, + getScreenX(humans[i].pos.x, player.camera), fix32ToInt(humans[i].pos.y), + TILE_ATTR(PAL0, 0, 0, 0)); + if(!humans[i].image){ + humans[i].active = FALSE; + return; + } +} + +static void updateHuman(u8 i){ + switch(humans[i].state){ + case HUMAN_WALKING: + // Y bounce: bob 4px around ground level + if(humans[i].pos.y >= GAME_H_F - FIX32(20) || humans[i].pos.y <= GAME_H_F - FIX32(28)) + humans[i].vel.y *= -1; + + // X wrap + if(humans[i].pos.x >= GAME_WRAP) + humans[i].pos.x -= GAME_WRAP; + if(humans[i].pos.x < 0) + humans[i].pos.x += GAME_WRAP; + + humans[i].pos.x += humans[i].vel.x; + humans[i].pos.y += humans[i].vel.y; + break; + + case HUMAN_CARRIED: + // follow carrier enemy position + if(humans[i].carriedBy >= 0 && enemies[humans[i].carriedBy].active){ + humans[i].pos.x = enemies[humans[i].carriedBy].pos.x; + humans[i].pos.y = enemies[humans[i].carriedBy].pos.y + FIX32(16); + } else { + // carrier died (shouldn't normally reach here, killEnemy handles it) + humans[i].state = HUMAN_FALLING; + humans[i].carriedBy = -1; + humans[i].vel.x = 0; + humans[i].vel.y = FIX32(2); + humanBeingCarried = FALSE; + } + break; + + case HUMAN_FALLING: + humans[i].pos.y += humans[i].vel.y; + // land on ground + if(humans[i].pos.y >= GAME_H_F - FIX32(24)){ + humans[i].pos.y = GAME_H_F - FIX32(24); + humans[i].state = HUMAN_WALKING; + fix32 speeds[] = { FIX32(0.3), FIX32(0.4), FIX32(0.5) }; + humans[i].vel.x = (random() % 2 == 0) ? speeds[random() % 3] : -speeds[random() % 3]; + humans[i].vel.y = (random() % 2 == 0) ? FIX32(0.1) : FIX32(-0.1); + } + break; + } + + // collect: check overlap with player (walking or falling only) + fix32 dx = getWrappedDelta(humans[i].pos.x, player.pos.x); + if(humans[i].state != HUMAN_CARRIED){ + fix32 dy = humans[i].pos.y - player.pos.y; + if(dx >= FIX32(-24) && dx <= FIX32(24) && dy >= FIX32(-24) && dy <= FIX32(24)){ + score += (humans[i].state == HUMAN_FALLING) ? 2000 : 1000; + sfxPickup(); + killHuman(i); + return; + } + } + + s16 sx = getScreenX(humans[i].pos.x, player.camera); + s16 sy = fix32ToInt(humans[i].pos.y); + bool visible = (dx >= -CULL_LIMIT && dx <= CULL_LIMIT); + SPR_setVisibility(humans[i].image, visible ? VISIBLE : HIDDEN); + SPR_setPosition(humans[i].image, sx, sy); +} + +void updateHumans(){ + for(s16 i = 0; i < HUMAN_COUNT; i++) + if(humans[i].active) + updateHuman(i); +} diff --git a/src/main.c b/src/main.c index 73be5eb..896b72c 100644 --- a/src/main.c +++ b/src/main.c @@ -5,6 +5,7 @@ #include "background.h" #include "bullets.h" #include "enemies.h" +#include "humans.h" #include "player.h" #include "stage.h" #include "chrome.h" @@ -35,6 +36,8 @@ static void updateGame(){ updateSfx(); if(clock % 2 == 0){ updateEnemies(); + if(!gameOver && enemyCount == 0) gameOver = TRUE; + updateHumans(); } else { updateBackground(); updateBullets(); diff --git a/src/player.h b/src/player.h index 75ff7bb..607dff1 100644 --- a/src/player.h +++ b/src/player.h @@ -9,8 +9,8 @@ #define PLAYER_BOUND_Y FIX32(PLAYER_OFF) #define PLAYER_BOUND_H FIX32(224 - PLAYER_OFF) -#define CAMERA_X FIX32(80) -#define CAMERA_W FIX32(240) +#define CAMERA_X FIX32(96) +#define CAMERA_W FIX32(224) #define SHOT_INTERVAL 15 @@ -106,7 +106,10 @@ static void shootPlayer(){ .angle = player.shotAngle, .player = TRUE }; - spawnBullet(spawner, EMPTY); + void updater(s16 i){ + if(bullets[i].clock == 5) killBullet(i, TRUE); + } + spawnBullet(spawner, updater); sfxPlayerShot(); shotClock = SHOT_INTERVAL; } else if(shotClock > 0) shotClock--; @@ -127,16 +130,21 @@ void loadPlayer(){ } void updatePlayer(){ - movePlayer(); - boundsPlayer(); - cameraPlayer(); - shootPlayer(); - - s16 sx = getScreenX(player.pos.x, player.camera); - s16 sy = fix32ToInt(player.pos.y); - - - SPR_setPosition(player.image, sx - PLAYER_OFF, sy - PLAYER_OFF); - - intToStr(fix32ToInt(player.pos.x), debugStr, 1); + if(!gameOver){ + if(player.recoveringClock > 0){ + if(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); + } + movePlayer(); + boundsPlayer(); + cameraPlayer(); + shootPlayer(); + s16 sx = getScreenX(player.pos.x, player.camera); + s16 sy = fix32ToInt(player.pos.y); + SPR_setPosition(player.image, sx - PLAYER_OFF, sy - PLAYER_OFF); + intToStr(fix32ToInt(player.pos.x), debugStr, 1); + } } \ No newline at end of file diff --git a/src/sfx.h b/src/sfx.h index 2bb4574..47209c4 100644 --- a/src/sfx.h +++ b/src/sfx.h @@ -1,10 +1,12 @@ static s16 sfxShotClock; static u16 sfxShotFreq; +#define SFX_VOL 2 + void sfxPlayerShot(){ sfxShotClock = 4; sfxShotFreq = 150; - PSG_setEnvelope(2, 2); + PSG_setEnvelope(2, SFX_VOL); PSG_setFrequency(2, sfxShotFreq); } @@ -14,43 +16,56 @@ 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, 3); + PSG_setEnvelope(1, SFX_VOL); PSG_setFrequency(1, sfxEnemyShotFreq); } // mid buzzy pulse - sits in the midrange void sfxEnemyShotB(){ + if(player.recoveringClock > 0) return; sfxEnemyShotClock = 5; sfxEnemyShotFreq = 400; sfxEnemyShotType = 1; - PSG_setEnvelope(1, 3); + PSG_setEnvelope(1, SFX_VOL); PSG_setFrequency(1, sfxEnemyShotFreq); } // quick rising ping - sweeps upward void sfxEnemyShotC(){ + if(player.recoveringClock > 0) return; sfxEnemyShotClock = 4; sfxEnemyShotFreq = 300; sfxEnemyShotType = 2; - PSG_setEnvelope(1, 3); + PSG_setEnvelope(1, SFX_VOL); PSG_setFrequency(1, sfxEnemyShotFreq); } +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, 0); + PSG_setEnvelope(3, SFX_VOL); } void updateSfx(){ if(sfxExpClock > 0){ sfxExpClock--; - PSG_setEnvelope(3, (18 - sfxExpClock) * 15 / 18); + PSG_setEnvelope(3, SFX_VOL + (18 - sfxExpClock) * (15 - SFX_VOL) / 18); if(sfxExpClock == 0){ PSG_setEnvelope(3, 15); } @@ -61,18 +76,28 @@ void updateSfx(){ else if(sfxEnemyShotType == 1) sfxEnemyShotFreq -= 50; else sfxEnemyShotFreq += 150; PSG_setFrequency(1, sfxEnemyShotFreq); - PSG_setEnvelope(1, 3 + (sfxEnemyShotType == 0 ? (3 - sfxEnemyShotClock) * 4 : - sfxEnemyShotType == 1 ? (5 - sfxEnemyShotClock) * 2 : - (4 - sfxEnemyShotClock) * 3)); + 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, 2 + (4 - sfxShotClock) * 3); + PSG_setEnvelope(2, SFX_VOL + (4 - sfxShotClock) * (15 - SFX_VOL) / 4); if(sfxShotClock == 0){ PSG_setEnvelope(2, 15); } diff --git a/src/stage.h b/src/stage.h index 723bcd3..f808115 100644 --- a/src/stage.h +++ b/src/stage.h @@ -1,9 +1,15 @@ void loadStage(){ - // Spawn 2 enemies per zone (4 zones = 8 total) + // Spawn 3 enemies per zone (4 zones = 12 total) for(u8 zone = 0; zone < 4; zone++){ for(u8 i = 0; i < 3; i++){ spawnEnemy(0, zone); } } + // Spawn 2 humans per zone (4 zones = 8 total) + for(u8 zone = 0; zone < 4; zone++){ + for(u8 i = 0; i < 2; i++){ + spawnHuman(zone); + } + } loadMap(); } \ No newline at end of file