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