cenotaph/src/treasure.h
2026-02-18 22:05:46 -05:00

159 lines
5.4 KiB
C

#define TREASURE_OFF 16
void spawnTreasure(u8 zone){
s16 i = -1;
for(s16 j = 0; j < TREASURE_COUNT; j++) if(!treasures[j].active) { i = j; break; }
if(i == -1) return;
treasures[i].active = TRUE;
treasures[i].state = TREASURE_WALKING;
treasures[i].carriedBy = -1;
fix32 zoneStart = FIX32(zone * 512);
treasures[i].pos.x = zoneStart + FIX32(random() % 512);
treasures[i].pos.y = GAME_H_F - FIX32(24);
fix32 speeds[] = { FIX32(0.3), FIX32(0.4), FIX32(0.5) };
treasures[i].vel.x = (random() % 2 == 0) ? speeds[random() % 3] : -speeds[random() % 3];
treasures[i].vel.y = (random() % 2 == 0) ? FIX32(0.1) : FIX32(-0.1);
treasures[i].image = SPR_addSprite(&treasureSprite,
getScreenX(treasures[i].pos.x, player.camera) - TREASURE_OFF, fix32ToInt(treasures[i].pos.y) - TREASURE_OFF,
TILE_ATTR(PAL0, 0, 0, 0));
if(!treasures[i].image){
treasures[i].active = FALSE;
return;
}
SPR_setVisibility(treasures[i].image, HIDDEN);
treasures[i].type = random() % 4;
SPR_setAnim(treasures[i].image, treasures[i].type);
}
static void updateTreasure(u8 i){
switch(treasures[i].state){
case TREASURE_WALKING:
// Y bounce: bob 4px around ground level
if(treasures[i].pos.y >= GAME_H_F - FIX32(20) || treasures[i].pos.y <= GAME_H_F - FIX32(28))
treasures[i].vel.y *= -1;
// X wrap
if(treasures[i].pos.x >= GAME_WRAP)
treasures[i].pos.x -= GAME_WRAP;
if(treasures[i].pos.x < 0)
treasures[i].pos.x += GAME_WRAP;
treasures[i].pos.x += treasures[i].vel.x;
treasures[i].pos.y += treasures[i].vel.y;
break;
case TREASURE_CARRIED:
// follow carrier enemy position
if(treasures[i].carriedBy >= 0 && enemies[treasures[i].carriedBy].active){
treasures[i].pos.x = enemies[treasures[i].carriedBy].pos.x;
treasures[i].pos.y = enemies[treasures[i].carriedBy].pos.y + FIX32(16);
} else {
// carrier died (shouldn't normally reach here, killEnemy handles it)
treasures[i].state = TREASURE_FALLING;
treasures[i].carriedBy = -1;
treasures[i].vel.x = 0;
treasures[i].vel.y = FIX32(3);
treasureBeingCarried = FALSE;
}
break;
case TREASURE_FALLING:
treasures[i].pos.y += treasures[i].vel.y;
// land on ground
if(treasures[i].pos.y >= GAME_H_F - FIX32(24)){
treasures[i].pos.y = GAME_H_F - FIX32(24);
treasures[i].state = TREASURE_WALKING;
fix32 speeds[] = { FIX32(0.3), FIX32(0.4), FIX32(0.5) };
treasures[i].vel.x = (random() % 2 == 0) ? speeds[random() % 3] : -speeds[random() % 3];
treasures[i].vel.y = (random() % 2 == 0) ? FIX32(0.1) : FIX32(-0.1);
}
break;
case TREASURE_COLLECTED: {
fix32 targetX, targetY;
if(treasures[i].trailIndex == 0){
targetX = player.pos.x;
targetY = player.pos.y + FIX32(24);
} else {
// find the treasure ahead in the chain
targetX = player.pos.x;
targetY = player.pos.y + FIX32(24);
for(s16 j = 0; j < TREASURE_COUNT; j++){
if(treasures[j].active && treasures[j].state == TREASURE_COLLECTED
&& treasures[j].trailIndex == treasures[i].trailIndex - 1){
targetX = treasures[j].pos.x;
targetY = treasures[j].pos.y + FIX32(8);
break;
}
}
}
fix32 deltaX = getWrappedDelta(targetX, treasures[i].pos.x);
treasures[i].pos.x += deltaX >> 2;
treasures[i].pos.y += (targetY - treasures[i].pos.y) >> 2;
// X wrap
if(treasures[i].pos.x >= GAME_WRAP)
treasures[i].pos.x -= GAME_WRAP;
if(treasures[i].pos.x < 0)
treasures[i].pos.x += GAME_WRAP;
break;
}
}
// collect: check overlap with player (walking or falling only)
fix32 dx = getWrappedDelta(treasures[i].pos.x, player.pos.x);
if(treasures[i].state != TREASURE_CARRIED && treasures[i].state != TREASURE_COLLECTED){
fix32 dy = treasures[i].pos.y - player.pos.y;
if(dx >= FIX32(-32) && dx <= FIX32(32) && dy >= FIX32(-32) && dy <= FIX32(32)){
score += (treasures[i].state == TREASURE_FALLING) ? 2000 : 1000;
sfxPickup();
treasureCollectedType = treasures[i].type;
treasureCollectedClock = 120;
// only add to trail if this type isn't already collected
bool duplicate = FALSE;
for(s16 j = 0; j < TREASURE_COUNT; j++){
if(treasures[j].active && treasures[j].state == TREASURE_COLLECTED
&& treasures[j].type == treasures[i].type){
duplicate = TRUE;
break;
}
}
if(duplicate){
killTreasure(i);
} else {
treasures[i].state = TREASURE_COLLECTED;
treasures[i].trailIndex = collectedCount++;
}
return;
}
}
s16 sx = getScreenX(treasures[i].pos.x, player.camera);
s16 sy = fix32ToInt(treasures[i].pos.y);
bool visible = (treasures[i].state == TREASURE_COLLECTED) || (dx >= -CULL_LIMIT && dx <= CULL_LIMIT);
if(visible && treasures[i].state == TREASURE_COLLECTED){
if(player.respawnClock > 0)
visible = FALSE;
else if(player.recoveringClock > 0)
visible = (player.recoveringClock % 20 > 10);
}
SPR_setVisibility(treasures[i].image, visible ? VISIBLE : HIDDEN);
SPR_setPosition(treasures[i].image, sx - TREASURE_OFF, sy - TREASURE_OFF);
// frame 0 = normal, frame 1 = flash, frame 2 = collected/faded
if(treasures[i].state == TREASURE_WALKING || treasures[i].state == TREASURE_FALLING)
SPR_setFrame(treasures[i].image, (clock / 15) & 1);
else if(treasures[i].state == TREASURE_COLLECTED)
SPR_setFrame(treasures[i].image, 2);
else
SPR_setFrame(treasures[i].image, 0);
}
void updateTreasures(){
for(s16 i = 0; i < TREASURE_COUNT; i++)
if(treasures[i].active)
updateTreasure(i);
}