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

@ -1,17 +1,20 @@
#define MAP_I 512
#define MAP_TILE TILE_ATTR_FULL(PAL0, 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_TREASURE_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 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)
#define FONT_BIG_I 256
u16 hudPal = PAL0;
#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(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);
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);
}
}
}
@ -31,7 +34,7 @@ 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);
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;
}
@ -57,17 +60,17 @@ static void drawScore(){
void loadMap(){
VDP_fillTileMapRect(BG_A, MAP_TILE, MAP_X, MAP_Y, MAP_W, MAP_H);
VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(PAL0, 1, 0, 0, MAP_I + 4), MAP_X, MAP_Y - 1, MAP_W, 1);
VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(PAL0, 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 + 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(PAL0, 1, 0, 0, MAP_I + 6), MAP_X - 1, MAP_Y, 1, MAP_H);
VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(PAL0, 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 + 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(PAL0, 1, 0, 0, MAP_I + 7), MAP_X - 1, MAP_Y + MAP_H, 1, 1);
VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(PAL0, 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 + 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(PAL0, 1, 0, 0, MAP_I + 8), MAP_X - 1, MAP_Y - 1, 1, 1);
VDP_fillTileMapRect(BG_A, TILE_ATTR_FULL(PAL0, 1, 0, 1, MAP_I + 8), MAP_X + MAP_W, MAP_Y - 1, 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;
@ -96,7 +99,7 @@ static bool mapTileOccupied(s16 col, s16 row, s16 pRow){
static void updateMap(){
// compute new player row
s16 pRow = fix32ToInt(player.pos.y) / 75;
s16 pRow = F32_toInt(player.pos.y) / 75;
if(pRow < 0) pRow = 0;
if(pRow >= MAP_H) pRow = MAP_H - 1;
@ -109,10 +112,10 @@ static void updateMap(){
continue;
}
fix32 dx = getWrappedDelta(enemies[i].pos.x, player.pos.x);
s16 col = fix32ToInt(dx) / 54 + MAP_W / 2;
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 = fix32ToInt(enemies[i].pos.y) / 75;
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;
@ -127,10 +130,10 @@ static void updateMap(){
continue;
}
fix32 dx = getWrappedDelta(treasures[i].pos.x, player.pos.x);
s16 col = fix32ToInt(dx) / 54 + MAP_W / 2;
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 = fix32ToInt(treasures[i].pos.y) / 75;
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;
@ -173,7 +176,8 @@ static void updateMap(){
mapEnemyRow[i] = mapNewRow[i];
if(mapNewCol[i] < 0) continue;
if(mapNewCol[i] == MAP_W / 2 && mapNewRow[i] == pRow) continue;
VDP_setTileMapXY(BG_A, MAP_ENEMY_TILE, MAP_X + mapNewCol[i], MAP_Y + mapNewRow[i]);
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
@ -185,29 +189,68 @@ u8 phraseIndex[4];
s16 lastLevel;
static void drawLevel(){
if(isAttract) return;
char lvlStr[4];
uintToStr(level + 1, lvlStr, 1);
VDP_drawText("LVL", 1, 8);
VDP_drawText(lvlStr, 4, 8);
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 repaintHud(){
bigText(scoreStr, SCORE_X, SCORE_Y, FALSE);
drawLives();
repaintMap();
drawLevel();
}
void loadChrome(){
VDP_loadTileSet(imageFontBig.tileset, FONT_BIG_I, DMA);
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;
drawScore();
drawLives();
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){
@ -238,19 +281,28 @@ static void doGameOver(){
treasureCollectedClock = 0;
allTreasureCollected = FALSE;
hitMessageClock = 0;
VDP_clearText(9, 5, 22);
VDP_clearText(9, 5, 23);
VDP_drawText("GAME OVER", 15, 13);
VDP_drawText("PRESS ANY BUTTON", 12, 14);
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);
SPR_setPalette(player.image, PAL1);
hudPal = PAL1;
hudPal = PAL1;
repaintHud();
XGM2_pause();
VDP_drawText("PAUSE", 17, 13);
VDP_drawText("PAUSED", 17, PAUSE_Y);
}
static void clearPause(){
@ -258,13 +310,15 @@ static void clearPause(){
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);
SPR_setPalette(player.image, PAL0);
hudPal = PAL0;
repaintHud();
XGM2_resume();
VDP_clearText(17, 13, 5);
VDP_clearText(17, PAUSE_Y, 6);
}
u32 pauseClock;
static void updatePause(){
if(gameOver) return;
if(gameOver || isAttract || levelWaitClock > 0 || levelClearing) return;
if(ctrl.start){
if(!isPausing){
isPausing = TRUE;
@ -282,59 +336,90 @@ static void updatePause(){
}
if(paused){
if(pauseClock % 60 < 30)
VDP_drawText("PAUSE", 17, 13);
VDP_drawText("PAUSED", 17, PAUSE_Y);
else
VDP_clearText(17, 13, 5);
VDP_clearText(17, PAUSE_Y, 6);
pauseClock++;
if(pauseClock >= 240) pauseClock = 0;
}
}
#define TRANSITION_TREASURE_X 10
#define TRANSITION_TREASURE_Y 13
#define TRANSITION_LEVEL_X 12
#define TRANSITION_LEVEL_Y 15
void updateChrome(){
updatePause();
if(gameOver && !didGameOver) doGameOver();
if(didGameOver){
gameOverClock++;
if((gameOverClock > 120 && (ctrl.a || ctrl.b || ctrl.c || ctrl.start)) || gameOverClock > 900)
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(levelClearClock == 2){
char numStr[12];
char lvlStr[4];
uintToStr(level + 2, lvlStr, 1);
VDP_drawText("LEVEL ", 15, 13);
VDP_drawText(lvlStr, 21, 13);
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(levelClearClock >= 110){
VDP_clearText(15, 13, 10);
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);
}
return;
}
if(lastScore != score){
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(lastLives != player.lives) drawLives();
if(!isAttract && lastLives != player.lives) drawLives();
if(lastLevel != level) drawLevel();
if(treasureCollectedClock > 0 && levelWaitClock == 0){
if(treasureCollectedClock == 120){
VDP_clearText(10, 5, 22);
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);
VDP_drawText(phrase, 20 - len / 2, 5);
}
treasureCollectedClock--;
if(treasureCollectedClock == 0){
VDP_clearText(10, 5, 22);
// check if all treasures are collected or gone
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){
@ -344,26 +429,41 @@ void updateChrome(){
}
if(allDone && collectedCount > 0){
allTreasureCollected = TRUE;
VDP_drawText("ALL TREASURE COLLECTED", 9, 5);
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, 22);
VDP_clearText(9, 5, 23);
treasureCollectedClock = 0;
allTreasureCollected = FALSE;
VDP_drawText(hitMessageBullet ? "BLASTED" : "SMASHED", hitMessageBullet ? 16 : 16, 5);
VDP_drawText(hitMessageBullet ? "Got You!" : "Collision!", 20 - (hitMessageBullet ? 8 : 10) / 2, 5);
}
hitMessageClock--;
if(hitMessageClock == 0)
VDP_clearText(9, 5, 22);
VDP_clearText(9, 5, 23);
}
if(levelWaitClock == 240){
VDP_clearText(9, 5, 22);
if(levelWaitClock == 210){
VDP_clearText(9, 5, 23);
treasureCollectedClock = 0;
allTreasureCollected = FALSE;
VDP_drawText("ALL ENEMIES DESTROYED", 9, 5);
VDP_drawText("All Enemies Down!", 12, 5);
}
if(clock % 4 == 0) updateMap();
}