This commit is contained in:
t. boddy 2026-03-18 13:23:51 -04:00
parent 417cae168f
commit a8bc01bedd
59 changed files with 2053 additions and 1054 deletions

View file

@ -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);
}