- Add pickup system (bomb, spread, rapid, shield) with new sprites - Replace Docker build with native SGDK compile via m68k-elf-gcc - Rework enemy spawning, homing math, boss HP/number globals - Expand chrome: score popups, minimap, pause/game over improvements - Overhaul stage generation with threat-point system - Add explosion sprites, shield sprite, powerup sprite - Add tools/ for sprite downscaling utilities
596 lines
No EOL
20 KiB
C
596 lines
No EOL
20 KiB
C
#define MAP_I 328
|
|
#define MAP_TILE TILE_ATTR_FULL(hudPal, 1, 0, 0, MAP_I)
|
|
#define MAP_PLAYER_TILE TILE_ATTR_FULL(hudPal, 1, 0, 0, MAP_I + 1)
|
|
#define MAP_ENEMY_TILE TILE_ATTR_FULL(hudPal, 1, 0, 0, MAP_I + 2)
|
|
#define MAP_BOSS_TILE TILE_ATTR_FULL(hudPal, 1, 0, 0, MAP_I + 9)
|
|
#define MAP_TREASURE_TILE TILE_ATTR_FULL(hudPal, 1, 0, 0, MAP_I + 3)
|
|
#define MAP_BORDER_X_TILE TILE_ATTR_FULL(hudPal, 1, 0, 0, MAP_I + 4)
|
|
|
|
u16 hudPal = PAL0;
|
|
|
|
#define POPUP_COUNT 4
|
|
|
|
struct scorePopup {
|
|
bool active;
|
|
u8 clock;
|
|
u8 len;
|
|
s16 tileX, tileY;
|
|
char text[6];
|
|
};
|
|
struct scorePopup popups[POPUP_COUNT];
|
|
|
|
void spawnPopup(fix32 worldX, fix32 worldY, u32 value){
|
|
s16 slot = -1;
|
|
for(s16 i = 0; i < POPUP_COUNT; i++) if(!popups[i].active){ slot = i; break; }
|
|
if(slot == -1) return;
|
|
s16 screenX = getScreenX(worldX, player.camera);
|
|
s16 screenY = F32_toInt(worldY);
|
|
s16 tX = screenX / 8;
|
|
s16 tY = screenY / 8;
|
|
tX--;
|
|
if(tX < 0) tX = 0;
|
|
if(tX > 38) tX = 38;
|
|
if(tY < 6) tY = 6;
|
|
if(tY > 25) tY = 25;
|
|
popups[slot].tileX = tX;
|
|
popups[slot].tileY = tY;
|
|
uintToStr(value, popups[slot].text, 1);
|
|
popups[slot].len = strlen(popups[slot].text);
|
|
popups[slot].clock = 0;
|
|
popups[slot].active = TRUE;
|
|
bigText(popups[slot].text, tX, tY, TRUE);
|
|
}
|
|
|
|
static void updatePopups(){
|
|
for(s16 i = 0; i < POPUP_COUNT; i++){
|
|
if(!popups[i].active) continue;
|
|
popups[i].clock++;
|
|
if(popups[i].clock >= 24){
|
|
VDP_clearTileMapRect(BG_A, popups[i].tileX, popups[i].tileY, popups[i].len, 2);
|
|
popups[i].active = FALSE;
|
|
continue;
|
|
}
|
|
if(popups[i].clock % 8 == 0){
|
|
VDP_clearTileMapRect(BG_A, popups[i].tileX, popups[i].tileY, popups[i].len, 2);
|
|
popups[i].tileY--;
|
|
if(popups[i].tileY < 6) popups[i].tileY = 6;
|
|
bigText(popups[i].text, popups[i].tileX, popups[i].tileY, TRUE);
|
|
}
|
|
}
|
|
}
|
|
|
|
void clearPopups(){
|
|
for(s16 i = 0; i < POPUP_COUNT; i++){
|
|
if(popups[i].active){
|
|
VDP_clearTileMapRect(BG_A, popups[i].tileX, popups[i].tileY, popups[i].len, 2);
|
|
popups[i].active = FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
#define FONT_BIG_I 340
|
|
|
|
void bigText(char* str, u16 x, u16 y, bool shadow){
|
|
for(u8 i = 0; i < strlen(str); i++){
|
|
if(str[i] >= 48){
|
|
VDP_setTileMapXY(BG_A, TILE_ATTR_FULL(hudPal, 1, 0, 0, (shadow ? 32 : 0) + FONT_BIG_I + str[i] - 48), x + i, y);
|
|
VDP_setTileMapXY(BG_A, TILE_ATTR_FULL(hudPal, 1, 0, 0, (shadow ? 32 : 0) + FONT_BIG_I + 16 + str[i] - 48), x + i, y + 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
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(hudPal, 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 mapTreasureCol[TREASURE_COUNT], mapTreasureRow[TREASURE_COUNT];
|
|
s16 mapPlayerRow;
|
|
|
|
static void drawScore(){
|
|
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
|
|
void loadMap(){
|
|
VDP_fillTileMapRect(BG_A, MAP_TILE, MAP_X, MAP_Y, MAP_W, MAP_H);
|
|
|
|
VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(hudPal, 1, 0, 0, MAP_I + 4), MAP_X, MAP_Y - 1, MAP_W, 1);
|
|
VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(hudPal, 1, 0, 0, MAP_I + 5), MAP_X, MAP_Y + MAP_H, MAP_W, 1);
|
|
|
|
VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(hudPal, 1, 0, 0, MAP_I + 6), MAP_X - 1, MAP_Y, 1, MAP_H);
|
|
VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(hudPal, 1, 0, 1, MAP_I + 6), MAP_X + MAP_W, MAP_Y, 1, MAP_H);
|
|
|
|
VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(hudPal, 1, 0, 0, MAP_I + 7), MAP_X - 1, MAP_Y + MAP_H, 1, 1);
|
|
VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(hudPal, 1, 0, 1, MAP_I + 7), MAP_X + MAP_W, MAP_Y + MAP_H, 1, 1);
|
|
|
|
VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(hudPal, 1, 0, 0, MAP_I + 8), MAP_X - 1, MAP_Y - 1, 1, 1);
|
|
VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(hudPal, 1, 0, 1, MAP_I + 8), MAP_X + MAP_W, MAP_Y - 1, 1, 1);
|
|
|
|
for(s16 i = 0; i < ENEMY_COUNT; i++){
|
|
mapEnemyCol[i] = -1;
|
|
mapEnemyRow[i] = -1;
|
|
}
|
|
for(s16 i = 0; i < TREASURE_COUNT; i++){
|
|
mapTreasureCol[i] = -1;
|
|
mapTreasureRow[i] = -1;
|
|
}
|
|
mapPlayerRow = -1;
|
|
}
|
|
|
|
// temp arrays for new positions
|
|
s16 mapNewCol[ENEMY_COUNT], mapNewRow[ENEMY_COUNT];
|
|
s16 mapNewTreasureCol[TREASURE_COUNT], mapNewTreasureRow[TREASURE_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 < TREASURE_COUNT; i++)
|
|
if(mapNewTreasureCol[i] == col && mapNewTreasureRow[i] == row) return TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
static void updateMap(){
|
|
// compute new player row
|
|
s16 pRow = F32_toInt(player.pos.y) / 75;
|
|
if(pRow < 0) pRow = 0;
|
|
if(pRow >= MAP_H) pRow = MAP_H - 1;
|
|
|
|
// compute new enemy positions
|
|
for(s16 i = 0; i < ENEMY_COUNT; i++){
|
|
if(!enemies[i].active || enemies[i].image == NULL
|
|
|| enemies[i].pos.y < FIX32(0) || enemies[i].pos.y > GAME_H_F){
|
|
mapNewCol[i] = -1;
|
|
mapNewRow[i] = -1;
|
|
continue;
|
|
}
|
|
fix32 dx = getWrappedDelta(enemies[i].pos.x, player.pos.x);
|
|
s16 col = F32_toInt(dx) / MAP_SCALE + MAP_W / 2;
|
|
if(col < 0) col = 0;
|
|
if(col >= MAP_W) col = MAP_W - 1;
|
|
s16 row = F32_toInt(enemies[i].pos.y) / 75;
|
|
if(row < 0) row = 0;
|
|
if(row >= MAP_H) row = MAP_H - 1;
|
|
mapNewCol[i] = col;
|
|
mapNewRow[i] = row;
|
|
}
|
|
|
|
// compute new treasure positions
|
|
for(s16 i = 0; i < TREASURE_COUNT; i++){
|
|
if(!treasures[i].active || treasures[i].image == NULL || treasures[i].state == TREASURE_COLLECTED){
|
|
mapNewTreasureCol[i] = -1;
|
|
mapNewTreasureRow[i] = -1;
|
|
continue;
|
|
}
|
|
fix32 dx = getWrappedDelta(treasures[i].pos.x, player.pos.x);
|
|
s16 col = F32_toInt(dx) / MAP_SCALE + MAP_W / 2;
|
|
if(col < 0) col = 0;
|
|
if(col >= MAP_W) col = MAP_W - 1;
|
|
s16 row = F32_toInt(treasures[i].pos.y) / 75;
|
|
if(row < 0) row = 0;
|
|
if(row >= MAP_H) row = MAP_H - 1;
|
|
mapNewTreasureCol[i] = col;
|
|
mapNewTreasureRow[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))
|
|
VDP_setTileMapXY(BG_A, MAP_TILE, MAP_X + MAP_W / 2, MAP_Y + mapPlayerRow);
|
|
|
|
// clear old enemy tiles that moved or disappeared
|
|
for(s16 i = 0; i < ENEMY_COUNT; i++){
|
|
if(mapEnemyCol[i] < 0) continue;
|
|
if(mapEnemyCol[i] == mapNewCol[i] && mapEnemyRow[i] == mapNewRow[i]) continue;
|
|
if(!mapTileOccupied(mapEnemyCol[i], mapEnemyRow[i], pRow))
|
|
VDP_setTileMapXY(BG_A, MAP_TILE, MAP_X + mapEnemyCol[i], MAP_Y + mapEnemyRow[i]);
|
|
}
|
|
|
|
// clear old treasure tiles that moved or disappeared
|
|
for(s16 i = 0; i < TREASURE_COUNT; i++){
|
|
if(mapTreasureCol[i] < 0) continue;
|
|
if(mapTreasureCol[i] == mapNewTreasureCol[i] && mapTreasureRow[i] == mapNewTreasureRow[i]) continue;
|
|
if(!mapTileOccupied(mapTreasureCol[i], mapTreasureRow[i], pRow))
|
|
VDP_setTileMapXY(BG_A, MAP_TILE, MAP_X + mapTreasureCol[i], MAP_Y + mapTreasureRow[i]);
|
|
}
|
|
|
|
// draw treasure dots (skip if player occupies same tile)
|
|
for(s16 i = 0; i < TREASURE_COUNT; i++){
|
|
mapTreasureCol[i] = mapNewTreasureCol[i];
|
|
mapTreasureRow[i] = mapNewTreasureRow[i];
|
|
if(mapNewTreasureCol[i] < 0) continue;
|
|
if(mapNewTreasureCol[i] == MAP_W / 2 && mapNewTreasureRow[i] == pRow) continue;
|
|
VDP_setTileMapXY(BG_A, MAP_TREASURE_TILE, MAP_X + mapNewTreasureCol[i], MAP_Y + mapNewTreasureRow[i]);
|
|
}
|
|
|
|
// draw enemy dots (skip if player occupies same tile)
|
|
for(s16 i = 0; i < ENEMY_COUNT; i++){
|
|
mapEnemyCol[i] = mapNewCol[i];
|
|
mapEnemyRow[i] = mapNewRow[i];
|
|
if(mapNewCol[i] < 0) continue;
|
|
if(mapNewCol[i] == MAP_W / 2 && mapNewRow[i] == pRow) continue;
|
|
u16 eTile = (enemies[i].type == ENEMY_TYPE_BOSS) ? MAP_BOSS_TILE : MAP_ENEMY_TILE;
|
|
VDP_setTileMapXY(BG_A, eTile, MAP_X + mapNewCol[i], MAP_Y + mapNewRow[i]);
|
|
}
|
|
|
|
// draw player dot on top
|
|
mapPlayerRow = pRow;
|
|
VDP_setTileMapXY(BG_A, MAP_PLAYER_TILE, MAP_X + MAP_W / 2, MAP_Y + pRow);
|
|
}
|
|
|
|
// pickup HUD tracking
|
|
s16 lastBombCount = -1;
|
|
s16 lastPowerupState = -1; // 0=none, 1=spread, 2=rapid, 3=shield (composite)
|
|
|
|
u8 phraseIndex[4];
|
|
|
|
s16 lastLevel;
|
|
static void drawLevel(){
|
|
if(isAttract) return;
|
|
char lvlStr[4];
|
|
uintToStr(level + 1, lvlStr, 1);
|
|
VDP_setTextPalette(hudPal);
|
|
// VDP_drawText(lvlStr, 1, 7);
|
|
VDP_setTextPalette(PAL0);
|
|
lastLevel = level;
|
|
}
|
|
|
|
static void repaintMap(){
|
|
VDP_fillTileMapRect(BG_A, MAP_TILE, MAP_X, MAP_Y, MAP_W, MAP_H);
|
|
VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(hudPal, 1, 0, 0, MAP_I + 4), MAP_X, MAP_Y - 1, MAP_W, 1);
|
|
VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(hudPal, 1, 0, 0, MAP_I + 5), MAP_X, MAP_Y + MAP_H, MAP_W, 1);
|
|
VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(hudPal, 1, 0, 0, MAP_I + 6), MAP_X - 1, MAP_Y, 1, MAP_H);
|
|
VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(hudPal, 1, 0, 1, MAP_I + 6), MAP_X + MAP_W, MAP_Y, 1, MAP_H);
|
|
VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(hudPal, 1, 0, 0, MAP_I + 7), MAP_X - 1, MAP_Y + MAP_H, 1, 1);
|
|
VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(hudPal, 1, 0, 1, MAP_I + 7), MAP_X + MAP_W, MAP_Y + MAP_H, 1, 1);
|
|
VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(hudPal, 1, 0, 0, MAP_I + 8), MAP_X - 1, MAP_Y - 1, 1, 1);
|
|
VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(hudPal, 1, 0, 1, MAP_I + 8), MAP_X + MAP_W, MAP_Y - 1, 1, 1);
|
|
// redraw tracked dots
|
|
for(s16 i = 0; i < TREASURE_COUNT; i++)
|
|
if(mapTreasureCol[i] >= 0)
|
|
VDP_setTileMapXY(BG_A, MAP_TREASURE_TILE, MAP_X + mapTreasureCol[i], MAP_Y + mapTreasureRow[i]);
|
|
for(s16 i = 0; i < ENEMY_COUNT; i++)
|
|
if(mapEnemyCol[i] >= 0){
|
|
u16 eTile = (enemies[i].type == ENEMY_TYPE_BOSS) ? MAP_BOSS_TILE : MAP_ENEMY_TILE;
|
|
VDP_setTileMapXY(BG_A, eTile, MAP_X + mapEnemyCol[i], MAP_Y + mapEnemyRow[i]);
|
|
}
|
|
if(mapPlayerRow >= 0)
|
|
VDP_setTileMapXY(BG_A, MAP_PLAYER_TILE, MAP_X + MAP_W / 2, MAP_Y + mapPlayerRow);
|
|
}
|
|
|
|
static void drawBombCount(){
|
|
if(isAttract) return;
|
|
if(player.bombCount > 0){
|
|
char bStr[4] = "B:";
|
|
char numStr[2];
|
|
uintToStr(player.bombCount, numStr, 1);
|
|
bStr[2] = numStr[0];
|
|
bStr[3] = 0;
|
|
VDP_drawText(bStr, 1, 7);
|
|
} else {
|
|
VDP_clearText(1, 7, 3);
|
|
}
|
|
lastBombCount = player.bombCount;
|
|
}
|
|
|
|
static void drawPowerupIndicator(){
|
|
if(isAttract) return;
|
|
VDP_clearText(1, 8, 6);
|
|
if(player.hasShield)
|
|
VDP_drawText("SH", 1, 8);
|
|
else if(player.activePowerup == 1)
|
|
VDP_drawText("SPREAD", 1, 8);
|
|
else if(player.activePowerup == 2)
|
|
VDP_drawText("RAPID", 1, 8);
|
|
s16 state = player.activePowerup;
|
|
if(player.hasShield) state = 3;
|
|
lastPowerupState = state;
|
|
}
|
|
|
|
static void repaintHud(){
|
|
bigText(scoreStr, SCORE_X, SCORE_Y, FALSE);
|
|
drawLives();
|
|
repaintMap();
|
|
drawLevel();
|
|
drawBombCount();
|
|
drawPowerupIndicator();
|
|
}
|
|
|
|
void loadChrome(){
|
|
VDP_loadTileSet(imageFontBigger.tileset, FONT_BIG_I, DMA);
|
|
VDP_loadTileSet(imageFontBigShadow.tileset, FONT_BIG_I + 32, DMA);
|
|
VDP_loadTileSet(imageChromeLife.tileset, LIFE_I, DMA);
|
|
VDP_loadTileSet(imageChromeLife2.tileset, LIFE_I + 2, DMA);
|
|
VDP_loadTileSet(mapIndicator.tileset, MAP_I, DMA);
|
|
lastScore = 1;
|
|
if(!isAttract) drawScore();
|
|
if(!isAttract) drawLives();
|
|
drawLevel();
|
|
}
|
|
|
|
bool didGameOver;
|
|
u32 gameOverClock;
|
|
static bool gameOverFading;
|
|
static void doGameOver(){
|
|
didGameOver = TRUE;
|
|
// check and save high score
|
|
if(score > highScore){
|
|
highScore = score;
|
|
saveHighScore();
|
|
VDP_drawText("NEW HIGH SCORE!", 14, 15);
|
|
}
|
|
for(s16 i = 0; i < BULLET_COUNT; i++) if(bullets[i].active) SPR_setPalette(bullets[i].image, PAL1);
|
|
for(s16 i = 0; i < ENEMY_COUNT; i++) if(enemies[i].active) SPR_setPalette(enemies[i].image, PAL1);
|
|
for(s16 i = 0; i < TREASURE_COUNT; i++) if(treasures[i].active){
|
|
if(treasures[i].state == TREASURE_COLLECTED){
|
|
// spawn player bullet explosion at carried treasure position
|
|
struct bulletSpawner spawner = {
|
|
.x = treasures[i].pos.x, .y = treasures[i].pos.y,
|
|
.anim = 0, .speed = 0, .angle = 0, .player = TRUE
|
|
};
|
|
void noop(s16 j){ (void)j; }
|
|
spawnBullet(spawner, noop);
|
|
for(s16 j = BULLET_COUNT - 1; j >= 0; j--){
|
|
if(bullets[j].active
|
|
&& bullets[j].pos.x == treasures[i].pos.x && bullets[j].pos.y == treasures[i].pos.y){
|
|
killBullet(j, TRUE);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
killTreasure(i);
|
|
}
|
|
SPR_releaseSprite(player.image);
|
|
clearPickups();
|
|
|
|
// clear lives
|
|
VDP_clearTileMapRect(BG_A, LIVES_X, LIVES_Y, 1, 16);
|
|
|
|
// clear messages
|
|
treasureCollectedClock = 0;
|
|
allTreasureCollected = FALSE;
|
|
hitMessageClock = 0;
|
|
VDP_clearText(9, 5, 23);
|
|
|
|
hudPal = PAL1;
|
|
hudPal = PAL1;
|
|
repaintHud();
|
|
|
|
VDP_drawText("GAME OVER", 15, 14);
|
|
VDP_drawText("Press Any Button", 12, 16);
|
|
}
|
|
|
|
#define PAUSE_Y 15
|
|
|
|
static void showPause(){
|
|
for(s16 i = 0; i < BULLET_COUNT; i++) if(bullets[i].active) SPR_setPalette(bullets[i].image, PAL1);
|
|
for(s16 i = 0; i < ENEMY_COUNT; i++) if(enemies[i].active) SPR_setPalette(enemies[i].image, PAL1);
|
|
for(s16 i = 0; i < TREASURE_COUNT; i++) if(treasures[i].active) SPR_setPalette(treasures[i].image, PAL1);
|
|
for(s16 i = 0; i < PICKUP_COUNT; i++) if(pickups[i].active) SPR_setPalette(pickups[i].image, PAL1);
|
|
SPR_setPalette(player.image, PAL1);
|
|
hudPal = PAL1;
|
|
hudPal = PAL1;
|
|
repaintHud();
|
|
XGM2_pause();
|
|
VDP_drawText("PAUSED", 17, PAUSE_Y);
|
|
}
|
|
|
|
static void clearPause(){
|
|
for(s16 i = 0; i < BULLET_COUNT; i++) if(bullets[i].active) SPR_setPalette(bullets[i].image, PAL0);
|
|
for(s16 i = 0; i < ENEMY_COUNT; i++) if(enemies[i].active) SPR_setPalette(enemies[i].image, PAL0);
|
|
for(s16 i = 0; i < TREASURE_COUNT; i++) if(treasures[i].active) SPR_setPalette(treasures[i].image, PAL0);
|
|
for(s16 i = 0; i < PICKUP_COUNT; i++) if(pickups[i].active) SPR_setPalette(pickups[i].image, PAL0);
|
|
SPR_setPalette(player.image, PAL0);
|
|
hudPal = PAL0;
|
|
repaintHud();
|
|
XGM2_resume();
|
|
VDP_clearText(17, PAUSE_Y, 6);
|
|
}
|
|
|
|
u32 pauseClock;
|
|
static void updatePause(){
|
|
if(gameOver || isAttract || levelWaitClock > 0 || levelClearing) return;
|
|
if(ctrl.start){
|
|
if(!isPausing){
|
|
isPausing = TRUE;
|
|
if(!paused){
|
|
pauseClock = 0;
|
|
paused = TRUE;
|
|
showPause();
|
|
} else {
|
|
paused = FALSE;
|
|
clearPause();
|
|
}
|
|
}
|
|
} else {
|
|
isPausing = FALSE;
|
|
}
|
|
if(paused){
|
|
if(pauseClock % 60 < 30)
|
|
VDP_drawText("PAUSED", 17, PAUSE_Y);
|
|
else
|
|
VDP_clearText(17, PAUSE_Y, 6);
|
|
pauseClock++;
|
|
if(pauseClock >= 240) pauseClock = 0;
|
|
}
|
|
}
|
|
|
|
#define TRANSITION_TREASURE_X 10
|
|
#define TRANSITION_TREASURE_Y 15
|
|
|
|
#define TRANSITION_LEVEL_X 12
|
|
#define TRANSITION_LEVEL_Y 13
|
|
|
|
void updateChrome(){
|
|
updatePopups();
|
|
updatePause();
|
|
if(gameOver && !didGameOver) doGameOver();
|
|
if(didGameOver){
|
|
gameOverClock++;
|
|
if(!gameOverFading){
|
|
if((gameOverClock > 120 && (ctrl.a || ctrl.b || ctrl.c || ctrl.start)) || gameOverClock > 900){
|
|
gameOverFading = TRUE;
|
|
PAL_fadeOut(0, 47, 20, TRUE);
|
|
}
|
|
} else if(!PAL_isDoingFade()){
|
|
SYS_hardReset();
|
|
}
|
|
return;
|
|
}
|
|
// level transition overlay
|
|
if(levelClearing){
|
|
if(bonusStage) return;
|
|
if(levelClearClock == 2){
|
|
char numStr[12];
|
|
char lvlStr[4];
|
|
char livesStr[4];
|
|
score += 2048 + 1024 * level;
|
|
lastScore = score;
|
|
|
|
uintToStr(statTreasures, numStr, 1);
|
|
VDP_drawText("Collected", TRANSITION_TREASURE_X, TRANSITION_TREASURE_Y);
|
|
VDP_drawText(numStr, TRANSITION_TREASURE_X + 10, TRANSITION_TREASURE_Y);
|
|
VDP_drawText("Treasure", TRANSITION_TREASURE_X + 10 + 2, TRANSITION_TREASURE_Y);
|
|
if(statTreasures != 1) VDP_drawText("s", TRANSITION_TREASURE_X + 10 + 2 + 8, TRANSITION_TREASURE_Y);
|
|
|
|
uintToStr(level + 1, lvlStr, 1);
|
|
VDP_drawText("Completed Level", TRANSITION_LEVEL_X, TRANSITION_LEVEL_Y);
|
|
VDP_drawText(lvlStr, TRANSITION_LEVEL_X + 16, TRANSITION_LEVEL_Y);
|
|
|
|
uintToStr(lastScore, scoreStr, 1);
|
|
VDP_drawText("Score", TRANSITION_LEVEL_X, TRANSITION_LEVEL_Y + 3);
|
|
VDP_drawText(scoreStr, TRANSITION_LEVEL_X + 6, TRANSITION_LEVEL_Y + 3);
|
|
|
|
uintToStr(player.lives, livesStr, 1);
|
|
VDP_drawText(livesStr, TRANSITION_LEVEL_X, TRANSITION_LEVEL_Y + 5);
|
|
if(player.lives == 1)
|
|
VDP_drawText("Life Left", TRANSITION_LEVEL_X + 2, TRANSITION_LEVEL_Y + 5);
|
|
else
|
|
VDP_drawText("Lives Left", TRANSITION_LEVEL_X + 2, TRANSITION_LEVEL_Y + 5);
|
|
|
|
if(grazeCount > 0){
|
|
char grazeStr[8];
|
|
char grazePtsStr[12];
|
|
uintToStr(grazeCount, grazeStr, 1);
|
|
uintToStr(grazeCount * 64, grazePtsStr, 1);
|
|
VDP_drawText("Grazes", TRANSITION_LEVEL_X, TRANSITION_LEVEL_Y + 7);
|
|
VDP_drawText(grazeStr, TRANSITION_LEVEL_X + 7, TRANSITION_LEVEL_Y + 7);
|
|
VDP_drawText(grazePtsStr, TRANSITION_LEVEL_X + 7 + strlen(grazeStr) + 1, TRANSITION_LEVEL_Y + 7);
|
|
VDP_drawText("pts", TRANSITION_LEVEL_X + 7 + strlen(grazeStr) + 1 + strlen(grazePtsStr) + 1, TRANSITION_LEVEL_Y + 7);
|
|
}
|
|
|
|
if(levelPerfect){
|
|
score += 4096;
|
|
lastScore = score;
|
|
VDP_drawText("PERFECT! +4096", 13, TRANSITION_LEVEL_Y + 9);
|
|
}
|
|
|
|
}
|
|
if(levelClearClock >= 230){
|
|
VDP_clearText(0, TRANSITION_TREASURE_Y, 40);
|
|
VDP_clearText(0, TRANSITION_LEVEL_Y, 40);
|
|
VDP_clearText(0, TRANSITION_LEVEL_Y + 3, 40);
|
|
VDP_clearText(0, TRANSITION_LEVEL_Y + 5, 40);
|
|
VDP_clearText(0, TRANSITION_LEVEL_Y + 7, 40);
|
|
VDP_clearText(0, TRANSITION_LEVEL_Y + 9, 40);
|
|
}
|
|
return;
|
|
}
|
|
if(!isAttract && lastScore != score){
|
|
lastScore = score;
|
|
drawScore();
|
|
// check for extend
|
|
while(score >= nextExtendScore){
|
|
player.lives++;
|
|
nextExtendScore = (nextExtendScore * 5) / 2; // previous + previous * 1.5
|
|
drawLives();
|
|
}
|
|
}
|
|
if(!isAttract && lastLives != player.lives) drawLives();
|
|
if(lastLevel != level) drawLevel();
|
|
if(treasureCollectedClock > 0 && levelWaitClock == 0){
|
|
if(treasureCollectedClock == 120){
|
|
VDP_clearText(10, 5, 23);
|
|
// check if all treasures are now collected or gone
|
|
bool allDone = TRUE;
|
|
for(s16 j = 0; j < TREASURE_COUNT; j++){
|
|
if(treasures[j].active && treasures[j].state != TREASURE_COLLECTED){
|
|
allDone = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
if(allDone && collectedCount > 0){
|
|
allTreasureCollected = TRUE;
|
|
score += 4096;
|
|
VDP_drawText("All Treasure Found!", 11, 5);
|
|
} else {
|
|
const char* mirrorPhrases[] = {"Reflect the Depths", "Dig Deeper Within", "See What Shines Below", "Mirror of the Mine", "Look Back, Strike Back"};
|
|
const char* lampPhrases[] = {"Strike Light", "Let There Be Lode!", "Bright Idea Deep Down", "Illuminate the Vein", "Glow from Below"};
|
|
const char* scarfPhrases[] = {"Cozy in the Caves", "Wrap the Underworld", "Snug as Bedrock", "Style from the Strata", "Warm the Depths"};
|
|
const char* swordPhrases[] = {"Ore You Ready?", "Mined Your Step", "Cutting Edge Geology", "Strike the Vein", "Spirit Steel"};
|
|
const char** sets[] = {mirrorPhrases, lampPhrases, scarfPhrases, swordPhrases};
|
|
const char* phrase = sets[treasureCollectedType][phraseIndex[treasureCollectedType]];
|
|
phraseIndex[treasureCollectedType] = (phraseIndex[treasureCollectedType] + 1) % 5;
|
|
u8 len = strlen(phrase);
|
|
u8 phraseX = 20 - len / 2;
|
|
if(phraseX < 10) phraseX = 10;
|
|
VDP_drawText(phrase, phraseX, 5);
|
|
}
|
|
}
|
|
treasureCollectedClock--;
|
|
if(treasureCollectedClock == 0)
|
|
VDP_clearText(9, 5, 24);
|
|
}
|
|
if(hitMessageClock > 0){
|
|
if(hitMessageClock == 120){
|
|
VDP_clearText(9, 5, 23);
|
|
treasureCollectedClock = 0;
|
|
allTreasureCollected = FALSE;
|
|
VDP_drawText(hitMessageBullet ? "Got You!" : "Collision!", 20 - (hitMessageBullet ? 8 : 10) / 2, 5);
|
|
}
|
|
hitMessageClock--;
|
|
if(hitMessageClock == 0)
|
|
VDP_clearText(9, 5, 23);
|
|
}
|
|
if(levelWaitClock == 210){
|
|
VDP_clearText(9, 5, 23);
|
|
treasureCollectedClock = 0;
|
|
allTreasureCollected = FALSE;
|
|
VDP_drawText("All Enemies Down!", 12, 5);
|
|
}
|
|
// pickup HUD
|
|
if(!isAttract){
|
|
if(lastBombCount != player.bombCount) drawBombCount();
|
|
s16 curPowerup = player.activePowerup;
|
|
if(player.hasShield) curPowerup = 3;
|
|
if(lastPowerupState != curPowerup) drawPowerupIndicator();
|
|
}
|
|
if(clock % 4 == 0) updateMap();
|
|
} |