diff --git a/.gitignore b/.gitignore index 01131ed..8c4096a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -CLAUDE.md +*.md .DS_Store **/.DS_Store Thumbs.db diff --git a/res/bonus.png b/res/bonus.png new file mode 100644 index 0000000..9ddcbca Binary files /dev/null and b/res/bonus.png differ diff --git a/res/bonusbg.png b/res/bonusbg.png new file mode 100644 index 0000000..baf5b37 Binary files /dev/null and b/res/bonusbg.png differ diff --git a/res/resources.res b/res/resources.res index 1cb9f33..e928271 100644 --- a/res/resources.res +++ b/res/resources.res @@ -31,6 +31,9 @@ SPRITE boss3Sprite "enemies/boss3.png" 6 6 NONE 0 SPRITE boss4Sprite "enemies/boss4.png" 6 6 NONE 0 SPRITE treasureSprite "treasure.png" 4 4 NONE 0 +SPRITE bonusObjSprite "bonus.png" 2 2 NONE 0 +TILESET bonusBgTiles "bonusbg.png" NONE + IMAGE mapIndicator "mapindicator.png" NONE NONE TILESET starTiles "stars.png" NONE diff --git a/res/stars.png b/res/stars.png index 649562a..e414871 100644 Binary files a/res/stars.png and b/res/stars.png differ diff --git a/src/bonus.h b/src/bonus.h new file mode 100644 index 0000000..5cd46c0 --- /dev/null +++ b/src/bonus.h @@ -0,0 +1,469 @@ +// Bonus Stage: minigame after boss fights +// Starfield background, sprites for objects. Stars = score, bombs = strikes. +// bonusObjSprite: anim 0 = star, anim 1 = bomb, frames 0-7 = depth (far to near) + +#define BONUS_OBJ_COUNT 48 + +#define BONUS_VP_X 160 // vanishing point X (screen center) +#define BONUS_VP_Y 112 // vanishing point Y (screen center) + +#define BONUS_RING_CX 160 +#define BONUS_RING_CY 112 +#define BONUS_RING_RX 120 +#define BONUS_RING_RY 80 +#define BONUS_ANGLE_SPEED FIX16(4) // max angular velocity +#define BONUS_ANGLE_ACCEL (BONUS_ANGLE_SPEED >> 3) // acceleration per frame +#define BONUS_ANGLE_FRICTION (BONUS_ANGLE_SPEED >> 3) // deceleration when no input + +#define BONUS_COLLISION_DIST 24 +#define BONUS_BOMB_COLLISION_DIST 14 +#define BONUS_NEAR_SCALE FIX16(54) // collision window start +#define BONUS_KILL_SCALE FIX16(192) // fallback removal for objects stuck near center + +// Depth frame thresholds (scale values, 8 stages like starfield brightness) +// Evenly spaced across 0 to BONUS_NEAR_SCALE +#define BONUS_DEPTH_COUNT 8 +static const fix16 bonusDepthThresholds[BONUS_DEPTH_COUNT] = { + FIX16(0), FIX16(8), FIX16(16), FIX16(24), FIX16(32), FIX16(40), FIX16(48), FIX16(54) +}; + +typedef struct { + bool active; + bool isBomb; + u8 depthFrame; // current depth frame (0-3) + fix16 scale; + fix16 scaleSpeed; + fix16 targetAngle; + fix16 angleVel; + s16 targetX, targetY; + Sprite* image; +} BonusObj; + +static BonusObj bonusObjs[BONUS_OBJ_COUNT]; +static Sprite* bonusCursor; +static fix16 bonusAngle; +static fix16 bonusAngleVel; +static s16 bonusCursorX, bonusCursorY; + +static u32 bonusScoreVal; +static u16 bonusStarCount; +static u32 bonusCl; +bool bonusActive; +static bool bonusExiting; +static u32 bonusExitClock; +static bool bonusPaused; +static bool bonusPauseInput; +static u32 bonusPauseClock; + +// --- Pattern system --- + +typedef struct { + s16 angleOffset; // degrees offset from pattern's base angle + u8 isBomb; // 0 = star, 1 = bomb + u8 delay; // frames to wait BEFORE spawning this entry (0 = same frame as previous) +} BonusPatternEntry; + +typedef struct { + const BonusPatternEntry* entries; + u8 count; +} BonusPattern; + +// Pattern definitions + +// Single star +static const BonusPatternEntry patSingleEntries[] = { + {0, 0, 0} +}; +static const BonusPattern patSingle = {patSingleEntries, 1}; + +// 3 stars in a row, same spot +static const BonusPatternEntry patTripleEntries[] = { + {0, 0, 0}, {0, 0, 8}, {0, 0, 8} +}; +static const BonusPattern patTriple = {patTripleEntries, 3}; + +// 5 stars in a row, same spot +static const BonusPatternEntry patStreamEntries[] = { + {0, 0, 0}, {0, 0, 8}, {0, 0, 8}, {0, 0, 8}, {0, 0, 8} +}; +static const BonusPattern patStream = {patStreamEntries, 5}; + +// 3 stars then a bomb — must dodge after collecting +static const BonusPatternEntry patLineBombEntries[] = { + {0, 0, 0}, {0, 0, 8}, {0, 0, 8}, {0, 1, 8} +}; +static const BonusPattern patLineBomb = {patLineBombEntries, 4}; + +// Bomb-star-bomb sandwich — must thread through +static const BonusPatternEntry patSandwichEntries[] = { + {0, 1, 0}, {0, 0, 8}, {0, 1, 8} +}; +static const BonusPattern patSandwich = {patSandwichEntries, 3}; + +// Single bomb +static const BonusPatternEntry patBombEntries[] = { + {0, 1, 0} +}; +static const BonusPattern patBomb = {patBombEntries, 1}; + +// Double bomb, same spot +static const BonusPatternEntry patBombPairEntries[] = { + {0, 1, 0}, {0, 1, 8} +}; +static const BonusPattern patBombPair = {patBombPairEntries, 2}; + +// Tier pools (expanding by boss number) +static const BonusPattern* tierPool0[] = {&patSingle, &patTriple, &patBomb}; +static const BonusPattern* tierPool1[] = {&patSingle, &patTriple, &patStream, &patBomb}; +static const BonusPattern* tierPool2[] = {&patSingle, &patTriple, &patStream, &patLineBomb, &patBomb, &patBombPair}; +static const BonusPattern* tierPool3[] = {&patSingle, &patTriple, &patStream, &patLineBomb, &patSandwich, &patBomb, &patBombPair}; +static const BonusPattern* tierPool4[] = {&patSingle, &patTriple, &patStream, &patLineBomb, &patSandwich, &patBomb, &patBombPair}; + +static const BonusPattern** tierPools[] = {tierPool0, tierPool1, tierPool2, tierPool3, tierPool4}; +static const u8 tierPoolCounts[] = {3, 4, 6, 7, 7}; +static const u8 tierCooldowns[] = {24, 18, 14, 10, 8}; + +// Spiral: cycles through 0°/45°/90° sweep, alternating CW/CCW +static s8 bonusSpiralDir; +static u8 bonusSpiralPhase; // 0=straight, 1=45°, 2=90° +static const fix16 bonusSpiralMul[3] = {0, FIX16(0.556), FIX16(1.111)}; + +// Sequencer state +static const BonusPattern* bonusCurrentPattern; +static u8 bonusPatternIndex; +static u8 bonusPatternDelay; +static fix16 bonusPatternBase; +static u8 bonusPatternCooldown; +static u8 bonusBossNum; + +#define BONUS_PAUSE_Y 14 + +static void drawBonusStars(){ + char buf[6]; + uintToStr(bonusStarCount, buf, 1); + bigText(buf, 1, 1, FALSE); +} + +// --- Object spawning and update --- + +static void spawnBonusObjAt(fix16 angle, bool isBomb){ + s16 slot = -1; + for(u8 i = 0; i < BONUS_OBJ_COUNT; i++){ + if(!bonusObjs[i].active){ slot = i; break; } + } + if(slot < 0) return; + + BonusObj* obj = &bonusObjs[slot]; + obj->isBomb = isBomb; + + obj->targetAngle = F16_normalizeAngle(angle); + obj->targetX = BONUS_RING_CX + F16_toInt(F16_mul(F16_cos(obj->targetAngle), FIX16(BONUS_RING_RX))); + obj->targetY = BONUS_RING_CY + F16_toInt(F16_mul(F16_sin(obj->targetAngle), FIX16(BONUS_RING_RY))); + + obj->scale = 0; + obj->scaleSpeed = FIX16(0.5); + if(bonusCl > 300) obj->scaleSpeed += FIX16(0.5); + if(bonusCl > 900) obj->scaleSpeed += FIX16(0.5); + + // angleVel: sweep 0°/45°/90° over lifetime depending on phase + obj->angleVel = bonusSpiralDir * F16_mul(bonusSpiralMul[bonusSpiralPhase], obj->scaleSpeed); + + obj->depthFrame = 0; + obj->image = SPR_addSprite(&bonusObjSprite, -16, -16, TILE_ATTR(PAL0, FALSE, FALSE, FALSE)); + if(obj->image){ + SPR_setAnim(obj->image, obj->isBomb ? 1 : 0); + SPR_setFrame(obj->image, 0); + obj->active = TRUE; + } +} + +static void updateBonusObj(u8 i){ + BonusObj* obj = &bonusObjs[i]; + obj->scale += obj->scaleSpeed; + + // Spiral: rotate target angle and recompute target position on ellipse + obj->targetAngle = F16_normalizeAngle(obj->targetAngle + obj->angleVel); + obj->targetX = BONUS_RING_CX + F16_toInt(F16_mul(F16_cos(obj->targetAngle), FIX16(BONUS_RING_RX))); + obj->targetY = BONUS_RING_CY + F16_toInt(F16_mul(F16_sin(obj->targetAngle), FIX16(BONUS_RING_RY))); + + // Update depth frame based on scale thresholds (0-7) + u8 newFrame = 0; + for(u8 f = BONUS_DEPTH_COUNT - 1; f > 0; f--){ + if(obj->scale >= bonusDepthThresholds[f]){ newFrame = f; break; } + } + if(newFrame != obj->depthFrame && obj->image){ + obj->depthFrame = newFrame; + SPR_setFrame(obj->image, newFrame); + } + + // Interpolate screen position from vanishing point toward target + fix16 diffX = (fix16)(obj->targetX - BONUS_VP_X); + fix16 diffY = (fix16)(obj->targetY - BONUS_VP_Y); + s16 sx = BONUS_VP_X + F16_toInt(F16_mul(diffX, obj->scale)); + s16 sy = BONUS_VP_Y + F16_toInt(F16_mul(diffY, obj->scale)); + + // Collision check when near player depth + if(obj->scale >= BONUS_NEAR_SCALE && obj->scale < BONUS_KILL_SCALE){ + s16 dx = sx - bonusCursorX; + s16 dy = sy - bonusCursorY; + s32 distSq = (s32)dx * dx + (s32)dy * dy; + s32 collDist = obj->isBomb ? BONUS_BOMB_COLLISION_DIST : BONUS_COLLISION_DIST; + if(distSq < collDist * collDist){ + if(obj->isBomb){ + sfxPlayerHit(); + bonusExiting = TRUE; + bonusExitClock = 0; + if(bonusCursor) SPR_setVisibility(bonusCursor, HIDDEN); + for(u8 k = 0; k < BONUS_OBJ_COUNT; k++){ + if(bonusObjs[k].active && bonusObjs[k].image){ + SPR_releaseSprite(bonusObjs[k].image); + bonusObjs[k].image = NULL; + } + bonusObjs[k].active = FALSE; + } + return; + } else { + bonusScoreVal += 512; + score += 512; + bonusStarCount++; + sfxPickup(); + drawBonusStars(); + } + SPR_releaseSprite(obj->image); + obj->image = NULL; + obj->active = FALSE; + return; + } + } + + // Remove if off-screen or scale fallback (for objects targeting near center) + if(sx < -16 || sx > 336 || sy < -16 || sy > 240 || obj->scale >= BONUS_KILL_SCALE){ + SPR_releaseSprite(obj->image); + obj->image = NULL; + obj->active = FALSE; + return; + } + + // Position sprite (center 16x16) + if(obj->image) + SPR_setPosition(obj->image, sx - 8, sy - 8); +} + +// --- Load / Update / Clear --- + +void loadBonus(u8 variant){ + // Use starfield system with bonusBg tiles, alternating between variants 0 and 1 + (void)variant; + loadStarfield(0); + VDP_loadTileSet(&bonusBgTiles, STAR_TILE_I, DMA); + starAlternate = TRUE; + + // Release the main game player sprite (hidden by clearLevel, but still allocated) + if(player.image){ + SPR_releaseSprite(player.image); + player.image = NULL; + } + + // Allocate cursor sprite — start at top of ring (270°) + bonusAngle = FIX16(270); + bonusAngleVel = 0; + bonusCursorX = BONUS_RING_CX + F16_toInt(F16_mul(F16_cos(bonusAngle), FIX16(BONUS_RING_RX))); + bonusCursorY = BONUS_RING_CY + F16_toInt(F16_mul(F16_sin(bonusAngle), FIX16(BONUS_RING_RY))); + bonusCursor = SPR_addSprite(&momoyoSprite, bonusCursorX - 24, bonusCursorY - 24, + TILE_ATTR(PAL0, FALSE, FALSE, FALSE)); + if(bonusCursor) SPR_setVisibility(bonusCursor, VISIBLE); + + // Init objects + for(u8 i = 0; i < BONUS_OBJ_COUNT; i++){ + bonusObjs[i].active = FALSE; + bonusObjs[i].image = NULL; + } + + // Init state + bonusScoreVal = 0; + bonusStarCount = 0; + bonusCl = 0; + bonusActive = TRUE; + + // Init pattern sequencer + bonusBossNum = level / 3; + if(bonusBossNum > 4) bonusBossNum = 4; + bonusCurrentPattern = NULL; + bonusPatternIndex = 0; + bonusPatternDelay = 0; + bonusPatternBase = 0; + bonusPatternCooldown = tierCooldowns[bonusBossNum]; + bonusSpiralDir = 1; + bonusSpiralPhase = 0; + bonusExiting = FALSE; + bonusExitClock = 0; + bonusPaused = FALSE; + bonusPauseInput = FALSE; + bonusPauseClock = 0; + + // HUD on BG_A + drawBonusStars(); + +#if MUSIC_VOLUME > 0 + XGM2_play(treasureMusic); +#endif +} + +void updateBonus(){ + // --- Pause --- + if(!bonusExiting){ + if(ctrl.start){ + if(!bonusPauseInput){ + bonusPauseInput = TRUE; + if(!bonusPaused){ + bonusPaused = TRUE; + bonusPauseClock = 0; + if(bonusCursor) SPR_setPalette(bonusCursor, PAL1); + for(u8 i = 0; i < BONUS_OBJ_COUNT; i++) + if(bonusObjs[i].active && bonusObjs[i].image) SPR_setPalette(bonusObjs[i].image, PAL1); + XGM2_pause(); + VDP_drawText("PAUSED", 17, BONUS_PAUSE_Y); + } else { + bonusPaused = FALSE; + if(bonusCursor) SPR_setPalette(bonusCursor, PAL0); + for(u8 i = 0; i < BONUS_OBJ_COUNT; i++) + if(bonusObjs[i].active && bonusObjs[i].image) SPR_setPalette(bonusObjs[i].image, PAL0); + XGM2_resume(); + VDP_clearText(17, BONUS_PAUSE_Y, 6); + } + } + } else { + bonusPauseInput = FALSE; + } + } + if(bonusPaused){ + bonusPauseClock++; + if(bonusPauseClock % 60 < 30) + VDP_drawText("PAUSED", 17, BONUS_PAUSE_Y); + else + VDP_clearText(17, BONUS_PAUSE_Y, 6); + if(bonusPauseClock >= 240) bonusPauseClock = 0; + return; + } + + bonusCl++; + + // Alternate bg variant every 60 frames + if(starAlternate && bonusCl % 30 == 0) + starVariant ^= 1; + + // Starfield animates only when not paused + updateStarfield(); + + // --- Exit sequence --- + if(bonusExiting){ + bonusExitClock++; + if(bonusExitClock == 1){ + VDP_drawText("BONUS FINISH", 13, 14); + char bonusStr[8]; + uintToStr(bonusScoreVal, bonusStr, 1); + VDP_drawText("Bonus", 14, 16); + VDP_drawText(bonusStr, 20, 16); + sfxCollectAllTreasures(); + } + if(bonusExitClock == 220) + PAL_fadeOut(0, 31, 20, TRUE); + if(bonusExitClock >= 240) + bonusActive = FALSE; + return; + } + + // --- Move cursor along elliptical ring (momentum-based) --- + if(ctrl.right || ctrl.left || ctrl.up || ctrl.down){ + s16 ix = (ctrl.right ? 1 : 0) - (ctrl.left ? 1 : 0); + s16 iy = (ctrl.down ? 1 : 0) - (ctrl.up ? 1 : 0); + fix16 targetAngle = F16_atan2(FIX16(iy), FIX16(ix)); + fix16 diff = targetAngle - bonusAngle; + if(diff > FIX16(180)) diff -= FIX16(360); + if(diff < FIX16(-180)) diff += FIX16(360); + // Determine target angular velocity from shortest-path direction + fix16 targetVel = (diff > 0) ? BONUS_ANGLE_SPEED : -BONUS_ANGLE_SPEED; + // Accelerate toward target velocity + if(bonusAngleVel < targetVel){ + bonusAngleVel += BONUS_ANGLE_ACCEL; + if(bonusAngleVel > targetVel) bonusAngleVel = targetVel; + } else if(bonusAngleVel > targetVel){ + bonusAngleVel -= BONUS_ANGLE_ACCEL; + if(bonusAngleVel < targetVel) bonusAngleVel = targetVel; + } + } else { + // Friction: decelerate toward 0 + if(bonusAngleVel > BONUS_ANGLE_FRICTION) bonusAngleVel -= BONUS_ANGLE_FRICTION; + else if(bonusAngleVel < -BONUS_ANGLE_FRICTION) bonusAngleVel += BONUS_ANGLE_FRICTION; + else bonusAngleVel = 0; + } + bonusAngle += bonusAngleVel; + bonusAngle = F16_normalizeAngle(bonusAngle); + s16 prevX = bonusCursorX; + bonusCursorX = BONUS_RING_CX + F16_toInt(F16_mul(F16_cos(bonusAngle), FIX16(BONUS_RING_RX))); + bonusCursorY = BONUS_RING_CY + F16_toInt(F16_mul(F16_sin(bonusAngle), FIX16(BONUS_RING_RY))); + if(bonusCursor){ + if(bonusCursorX < prevX) SPR_setHFlip(bonusCursor, TRUE); + else if(bonusCursorX > prevX) SPR_setHFlip(bonusCursor, FALSE); + SPR_setPosition(bonusCursor, bonusCursorX - 24, bonusCursorY - 24); + } + + // --- Pattern sequencer --- + if(bonusCurrentPattern){ + if(bonusPatternDelay > 0){ + bonusPatternDelay--; + } else { + const BonusPatternEntry* entry = &bonusCurrentPattern->entries[bonusPatternIndex]; + fix16 angle = bonusPatternBase + FIX16(entry->angleOffset); + spawnBonusObjAt(angle, entry->isBomb); + bonusPatternIndex++; + if(bonusPatternIndex < bonusCurrentPattern->count){ + bonusPatternDelay = bonusCurrentPattern->entries[bonusPatternIndex].delay; + } else { + bonusCurrentPattern = NULL; + bonusPatternCooldown = tierCooldowns[bonusBossNum]; + } + } + } else { + if(bonusPatternCooldown > 0){ + bonusPatternCooldown--; + } else { + u8 poolCount = tierPoolCounts[bonusBossNum]; + const BonusPattern* pat = tierPools[bonusBossNum][random() % poolCount]; + bonusSpiralPhase++; + if(bonusSpiralPhase > 2){ + bonusSpiralPhase = 0; + bonusSpiralDir *= -1; + } + bonusCurrentPattern = pat; + bonusPatternBase = FIX16(random() % 360); + bonusPatternIndex = 0; + bonusPatternDelay = pat->entries[0].delay; + } + } + + // --- Update objects --- + for(u8 i = 0; i < BONUS_OBJ_COUNT; i++){ + if(bonusObjs[i].active) updateBonusObj(i); + } +} + +void clearBonus(){ + // Release cursor sprite + if(bonusCursor){ + SPR_releaseSprite(bonusCursor); + bonusCursor = NULL; + } + // Release object sprites + for(u8 i = 0; i < BONUS_OBJ_COUNT; i++){ + if(bonusObjs[i].image){ + SPR_releaseSprite(bonusObjs[i].image); + bonusObjs[i].image = NULL; + } + bonusObjs[i].active = FALSE; + } + // Clear starfield tiles from BG_B + clearStarfield(); + // Clear bonus text from BG_A + VDP_clearTileMapRect(BG_A, 0, 0, 40, 32); +} diff --git a/src/chrome.h b/src/chrome.h index 9a26e40..43c3a8c 100644 --- a/src/chrome.h +++ b/src/chrome.h @@ -367,6 +367,7 @@ void updateChrome(){ } // level transition overlay if(levelClearing){ + if(bonusStage) return; if(levelClearClock == 2){ char numStr[12]; char lvlStr[4]; diff --git a/src/global.h b/src/global.h index 005695e..802b974 100644 --- a/src/global.h +++ b/src/global.h @@ -6,10 +6,13 @@ void sfxEnemyShotC(); void sfxExplosion(); void sfxPickup(); void sfxGraze(); +void sfxPlayerHit(); +void sfxCollectAllTreasures(); void loadMap(); void loadGame(); -#define SKIP_START 1 +#define SKIP_START 0 +#define SKIP_TO_BONUS 0 // 1 = boot straight into bonus stage for testing (0 for release) u32 clock; #define CLOCK_LIMIT 32000 @@ -69,7 +72,7 @@ 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) // #define START_LEVEL 0 // offset added to starting level (0 = normal start) s16 enemyCount, bulletCount; u8 level; @@ -84,6 +87,7 @@ bool hitMessageBullet; // TRUE = blasted, FALSE = smashed bool levelClearing; u32 levelClearClock; u8 levelWaitClock; +bool bonusStage; // controls struct controls { diff --git a/src/main.c b/src/main.c index d266886..a6f1959 100644 --- a/src/main.c +++ b/src/main.c @@ -11,6 +11,7 @@ #include "chrome.h" #include "start.h" #include "starfield.h" +#include "bonus.h" #include "sfx.h" static void loadInternals(){ @@ -70,7 +71,19 @@ void loadGame(){ killBullets = TRUE; attractEnding = FALSE; started = TRUE; +#if SKIP_TO_BONUS + clearLevel(); + loadBonus(level % 3); + bonusStage = TRUE; + levelClearing = TRUE; + levelClearClock = 1; + 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); +#else startLevelFadeIn(); +#endif } static void updateGame(){ @@ -94,11 +107,42 @@ static void updateGame(){ updateChrome(); if(levelClearing){ levelClearClock++; + // Bonus stage branch (after boss fights) + if(bonusStage){ + updateBonus(); + if(!bonusActive){ + bonusStage = FALSE; + clearBonus(); + levelClearing = FALSE; + player.pos.y = FIX32(112); + player.camera = player.pos.x - FIX32(160); + playerVelX = 0; + loadBackground(); + loadChrome(); + loadLevel(level + 1); + 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 + } + return; + } + // Starfield transition (non-boss levels) if(levelClearClock == 73) XGM2_stop(); if(levelClearClock == 1){ clearLevel(); - loadStarfield(level % 3); + if(isBossLevel(level) && !isAttract){ + loadBonus(level % 3); + bonusStage = TRUE; + } else { + loadStarfield(level % 3); + } u16 transPal[32]; memcpy(transPal, font.palette->data, 16 * sizeof(u16)); memcpy(transPal + 16, shadow.palette->data, 16 * sizeof(u16)); @@ -125,7 +169,7 @@ static void updateGame(){ if(!isAttract) XGM2_play(isBossLevel(level) ? bossMusic : stageMusic); #endif } - if(levelClearing) updateStarfield(); + if(levelClearing && !bonusStage) updateStarfield(); return; } if(levelWaitClock > 0){ diff --git a/src/player.h b/src/player.h index 4ab019d..0dd7c58 100644 --- a/src/player.h +++ b/src/player.h @@ -10,7 +10,7 @@ #define CAMERA_W FIX32(208) #define SHOT_INTERVAL 20 -#define PLAYER_SHOT_SPEED FIX32(18) +#define PLAYER_SHOT_SPEED FIX32(24) s16 shotClock; fix32 screenX; diff --git a/src/starfield.h b/src/starfield.h index 58a847e..b68e478 100644 --- a/src/starfield.h +++ b/src/starfield.h @@ -22,6 +22,7 @@ typedef struct { static StarParticle stars[STAR_COUNT]; static u8 spawnCounter; static u8 starVariant; +static bool starAlternate; // when TRUE, spawnStar toggles variant 0/1 static void spawnStar(u8 i){ fix16 angleDeg = FIX16(random() % 360); @@ -56,6 +57,7 @@ static void preadvanceStar(u8 i){ void loadStarfield(u8 variant){ starVariant = variant; + starAlternate = FALSE; 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);