pickups, native build, enemy/bullet/stage overhaul
- 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
This commit is contained in:
parent
3263b2597b
commit
073f96c9b1
25 changed files with 2320 additions and 1186 deletions
264
src/enemies.h
264
src/enemies.h
|
|
@ -42,11 +42,13 @@ void spawnEnemy(u8 type, u8 zone){
|
|||
|
||||
enemies[i].pos.x = randX;
|
||||
enemies[i].pos.y = randY;
|
||||
|
||||
// Default sprite — load functions can override via SPR_setDefinition()
|
||||
static const SpriteDefinition* bossSpriteDefs[4] = { &boss1Sprite, &boss2Sprite, &boss3Sprite, &boss4Sprite };
|
||||
SpriteDefinition const* spriteDef;
|
||||
if(type == ENEMY_TYPE_DRONE) spriteDef = &eyeBigSprite;
|
||||
else if(type == ENEMY_TYPE_BOSS) spriteDef = bossSpriteDefs[pendingBossNum % 4];
|
||||
if(type == ENEMY_TYPE_BOSS) spriteDef = bossSpriteDefs[pendingBossNum % 4];
|
||||
else spriteDef = &fairySprite;
|
||||
|
||||
enemies[i].off = (type == ENEMY_TYPE_BOSS) ? 24 : 16;
|
||||
enemies[i].image = SPR_addSprite(spriteDef,
|
||||
getScreenX(enemies[i].pos.x, player.camera) - enemies[i].off, F32_toInt(enemies[i].pos.y) - enemies[i].off, TILE_ATTR(PAL0, 0, 0, 0));
|
||||
|
|
@ -61,23 +63,62 @@ void spawnEnemy(u8 type, u8 zone){
|
|||
enemies[i].ints[j] = 0;
|
||||
enemies[i].fixes[j] = 0;
|
||||
}
|
||||
enemies[i].ints[3] = -1;
|
||||
enemies[i].canGrabTreasure = FALSE;
|
||||
enemies[i].homesOnPlayer = FALSE;
|
||||
enemies[i].canShoot = FALSE;
|
||||
enemies[i].canFlipH = FALSE;
|
||||
enemies[i].useBigSprite = FALSE;
|
||||
enemies[i].carriedTreasure = -1;
|
||||
enemies[i].targetTreasure = -1;
|
||||
enemies[i].anim = 0;
|
||||
switch(enemies[i].type){
|
||||
case ENEMY_TYPE_TEST:
|
||||
case ENEMY_TYPE_ONE:
|
||||
loadEnemyOne(i);
|
||||
break;
|
||||
case ENEMY_TYPE_DRONE:
|
||||
loadDrone(i);
|
||||
case ENEMY_TYPE_TWO:
|
||||
loadEnemyTwo(i);
|
||||
break;
|
||||
case ENEMY_TYPE_GUNNER:
|
||||
loadGunner(i);
|
||||
case ENEMY_TYPE_THREE:
|
||||
loadEnemyThree(i);
|
||||
break;
|
||||
case ENEMY_TYPE_HUNTER:
|
||||
loadHunter(i);
|
||||
case ENEMY_TYPE_FOUR:
|
||||
loadEnemyFour(i);
|
||||
break;
|
||||
case ENEMY_TYPE_BUILDER:
|
||||
loadBuilder(i);
|
||||
case ENEMY_TYPE_FIVE:
|
||||
loadEnemyFive(i);
|
||||
break;
|
||||
case ENEMY_TYPE_SIX:
|
||||
loadEnemySix(i);
|
||||
break;
|
||||
case ENEMY_TYPE_SEVEN:
|
||||
loadEnemySeven(i);
|
||||
break;
|
||||
case ENEMY_TYPE_EIGHT:
|
||||
loadEnemyEight(i);
|
||||
break;
|
||||
case ENEMY_TYPE_NINE:
|
||||
loadEnemyNine(i);
|
||||
break;
|
||||
case ENEMY_TYPE_TEN:
|
||||
loadEnemyTen(i);
|
||||
break;
|
||||
case ENEMY_TYPE_ELEVEN:
|
||||
loadEnemyEleven(i);
|
||||
break;
|
||||
case ENEMY_TYPE_TWELVE:
|
||||
loadEnemyTwelve(i);
|
||||
break;
|
||||
case ENEMY_TYPE_THIRTEEN:
|
||||
loadEnemyThirteen(i);
|
||||
break;
|
||||
case ENEMY_TYPE_FOURTEEN:
|
||||
loadEnemyFourteen(i);
|
||||
break;
|
||||
case ENEMY_TYPE_FIFTEEN:
|
||||
loadEnemyFifteen(i);
|
||||
break;
|
||||
case ENEMY_TYPE_SIXTEEN:
|
||||
loadEnemySixteen(i);
|
||||
break;
|
||||
case ENEMY_TYPE_BOSS:
|
||||
loadBoss(i);
|
||||
|
|
@ -89,11 +130,11 @@ void spawnEnemy(u8 type, u8 zone){
|
|||
}
|
||||
|
||||
static void boundsEnemy(u8 i){
|
||||
if((enemies[i].type == ENEMY_TYPE_TEST || enemies[i].type == ENEMY_TYPE_BUILDER) && enemies[i].ints[3] >= 0){
|
||||
s16 h = enemies[i].ints[3];
|
||||
if(enemies[i].canGrabTreasure && enemies[i].carriedTreasure >= 0){
|
||||
s16 h = enemies[i].carriedTreasure;
|
||||
// if the treasure was collected by player or gone, kill this enemy
|
||||
if(!treasures[h].active || treasures[h].state == TREASURE_COLLECTED){
|
||||
enemies[i].ints[3] = -1;
|
||||
enemies[i].carriedTreasure = -1;
|
||||
treasureBeingCarried = FALSE;
|
||||
killEnemy(i);
|
||||
return;
|
||||
|
|
@ -108,17 +149,13 @@ static void boundsEnemy(u8 i){
|
|||
treasures[h].vel.x = 0;
|
||||
treasures[h].vel.y = FIX32(3);
|
||||
}
|
||||
enemies[i].ints[3] = -1;
|
||||
enemies[i].carriedTreasure = -1;
|
||||
treasureBeingCarried = FALSE;
|
||||
enemies[i].vel.y = FIX32(1);
|
||||
} else {
|
||||
if(treasures[h].active) killTreasure(h);
|
||||
enemies[i].ints[3] = -1;
|
||||
enemies[i].carriedTreasure = -1;
|
||||
treasureBeingCarried = FALSE;
|
||||
if(enemies[i].type == ENEMY_TYPE_BUILDER){
|
||||
u8 zone = F32_toInt(enemies[i].pos.x) / 512;
|
||||
spawnEnemy(ENEMY_TYPE_GUNNER, zone);
|
||||
}
|
||||
enemies[i].hp = 0;
|
||||
killEnemy(i);
|
||||
}
|
||||
|
|
@ -143,6 +180,64 @@ static void boundsEnemy(u8 i){
|
|||
}
|
||||
}
|
||||
|
||||
static void enemySeekTreasure(u8 i){
|
||||
// carrying: steer upward
|
||||
if(enemies[i].carriedTreasure >= 0){
|
||||
enemies[i].angle = FIX16(248 + (random() % 45));
|
||||
enemies[i].vel.x = F32_mul(F32_cos(enemies[i].angle), enemies[i].speed);
|
||||
enemies[i].vel.y = F32_mul(F32_sin(enemies[i].angle), enemies[i].speed);
|
||||
return;
|
||||
}
|
||||
|
||||
// cancel target if a treasure is already being carried
|
||||
if(treasureBeingCarried && enemies[i].targetTreasure >= 0){
|
||||
enemies[i].targetTreasure = -1;
|
||||
}
|
||||
|
||||
// scan for nearest walking treasure every 30 frames
|
||||
if(!treasureBeingCarried && enemies[i].clock % 30 == 0){
|
||||
s16 bestTreasure = -1;
|
||||
fix32 bestDist = FIX32(9999);
|
||||
for(s16 j = 0; j < TREASURE_COUNT; j++){
|
||||
if(!treasures[j].active || treasures[j].state != TREASURE_WALKING) continue;
|
||||
fix32 dx = getWrappedDelta(enemies[i].pos.x, treasures[j].pos.x);
|
||||
fix32 dy = enemies[i].pos.y - treasures[j].pos.y;
|
||||
fix32 dist = (dx < 0 ? -dx : dx) + (dy < 0 ? -dy : dy);
|
||||
if(dist < bestDist && dist < FIX32(256)){
|
||||
bestDist = dist;
|
||||
bestTreasure = j;
|
||||
}
|
||||
}
|
||||
enemies[i].targetTreasure = bestTreasure;
|
||||
}
|
||||
|
||||
// steer toward target treasure
|
||||
if(enemies[i].targetTreasure >= 0){
|
||||
s16 t = enemies[i].targetTreasure;
|
||||
if(!treasures[t].active || treasures[t].state != TREASURE_WALKING){
|
||||
enemies[i].targetTreasure = -1;
|
||||
} else {
|
||||
fix32 dx = getWrappedDelta(treasures[t].pos.x, enemies[i].pos.x);
|
||||
fix32 dy = treasures[t].pos.y - enemies[i].pos.y;
|
||||
|
||||
fix16 angle = getAngle(dx, dy);
|
||||
enemies[i].vel.x = F32_mul(F32_cos(angle), enemies[i].speed);
|
||||
enemies[i].vel.y = F32_mul(F32_sin(angle), enemies[i].speed);
|
||||
|
||||
// grab check: within 16px
|
||||
fix32 adx = dx < 0 ? -dx : dx;
|
||||
fix32 ady = dy < 0 ? -dy : dy;
|
||||
if(adx < FIX32(16) && ady < FIX32(16)){
|
||||
enemies[i].carriedTreasure = t;
|
||||
enemies[i].targetTreasure = -1;
|
||||
treasureBeingCarried = TRUE;
|
||||
treasures[t].state = TREASURE_CARRIED;
|
||||
treasures[t].carriedBy = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void updateEnemy(u8 i){
|
||||
enemies[i].pos.x += enemies[i].vel.x - (player.vel.x >> 3);
|
||||
enemies[i].pos.y += enemies[i].vel.y - (playerScrollVelY >> 3);
|
||||
|
|
@ -153,21 +248,67 @@ static void updateEnemy(u8 i){
|
|||
fix32 dx = getWrappedDelta(enemies[i].pos.x, player.pos.x);
|
||||
enemies[i].onScreen = (dx >= -CULL_LIMIT && dx <= CULL_LIMIT);
|
||||
|
||||
// flag-based treasure seeking (before type-specific update)
|
||||
if(enemies[i].canGrabTreasure){
|
||||
enemySeekTreasure(i);
|
||||
}
|
||||
|
||||
// flag-based homing
|
||||
if(enemies[i].homesOnPlayer){
|
||||
enemies[i].angle = enemyHoneAngle(i);
|
||||
if(player.respawnClock > 0) enemies[i].angle = F16_normalizeAngle(enemies[i].angle + FIX16(180));
|
||||
enemies[i].vel.x = F32_mul(F32_cos(enemies[i].angle), enemies[i].speed);
|
||||
enemies[i].vel.y = F32_mul(F32_sin(enemies[i].angle), enemies[i].speed);
|
||||
}
|
||||
|
||||
switch(enemies[i].type){
|
||||
case ENEMY_TYPE_TEST:
|
||||
case ENEMY_TYPE_ONE:
|
||||
updateEnemyOne(i);
|
||||
break;
|
||||
case ENEMY_TYPE_DRONE:
|
||||
updateDrone(i);
|
||||
case ENEMY_TYPE_TWO:
|
||||
updateEnemyTwo(i);
|
||||
break;
|
||||
case ENEMY_TYPE_GUNNER:
|
||||
updateGunner(i);
|
||||
case ENEMY_TYPE_THREE:
|
||||
updateEnemyThree(i);
|
||||
break;
|
||||
case ENEMY_TYPE_HUNTER:
|
||||
updateHunter(i);
|
||||
case ENEMY_TYPE_FOUR:
|
||||
updateEnemyFour(i);
|
||||
break;
|
||||
case ENEMY_TYPE_BUILDER:
|
||||
updateBuilder(i);
|
||||
case ENEMY_TYPE_FIVE:
|
||||
updateEnemyFive(i);
|
||||
break;
|
||||
case ENEMY_TYPE_SIX:
|
||||
updateEnemySix(i);
|
||||
break;
|
||||
case ENEMY_TYPE_SEVEN:
|
||||
updateEnemySeven(i);
|
||||
break;
|
||||
case ENEMY_TYPE_EIGHT:
|
||||
updateEnemyEight(i);
|
||||
break;
|
||||
case ENEMY_TYPE_NINE:
|
||||
updateEnemyNine(i);
|
||||
break;
|
||||
case ENEMY_TYPE_TEN:
|
||||
updateEnemyTen(i);
|
||||
break;
|
||||
case ENEMY_TYPE_ELEVEN:
|
||||
updateEnemyEleven(i);
|
||||
break;
|
||||
case ENEMY_TYPE_TWELVE:
|
||||
updateEnemyTwelve(i);
|
||||
break;
|
||||
case ENEMY_TYPE_THIRTEEN:
|
||||
updateEnemyThirteen(i);
|
||||
break;
|
||||
case ENEMY_TYPE_FOURTEEN:
|
||||
updateEnemyFourteen(i);
|
||||
break;
|
||||
case ENEMY_TYPE_FIFTEEN:
|
||||
updateEnemyFifteen(i);
|
||||
break;
|
||||
case ENEMY_TYPE_SIXTEEN:
|
||||
updateEnemySixteen(i);
|
||||
break;
|
||||
case ENEMY_TYPE_BOSS:
|
||||
updateBoss(i);
|
||||
|
|
@ -180,46 +321,43 @@ static void updateEnemy(u8 i){
|
|||
fix32 edy = enemies[i].pos.y - player.pos.y;
|
||||
if(edx >= FIX32(-16) && edx <= FIX32(16) && edy >= FIX32(-16) && edy <= FIX32(16)){
|
||||
sfxExplosion();
|
||||
// spawn explosion at player position
|
||||
s16 expSlot = -1;
|
||||
for(s16 j = 0; j < BULLET_COUNT; j++) if(!bullets[j].active){ expSlot = j; break; }
|
||||
if(expSlot >= 0){
|
||||
bullets[expSlot].active = TRUE;
|
||||
bullets[expSlot].player = TRUE;
|
||||
bullets[expSlot].explosion = TRUE;
|
||||
bullets[expSlot].pos.x = player.pos.x;
|
||||
bullets[expSlot].pos.y = player.pos.y;
|
||||
bullets[expSlot].vel.x = 0;
|
||||
bullets[expSlot].vel.y = 0;
|
||||
bullets[expSlot].clock = 0;
|
||||
bullets[expSlot].frame = 0;
|
||||
bullets[expSlot].image = SPR_addSprite(&pBulletSprite, -32, -32, TILE_ATTR(PAL0, 0, 0, 0));
|
||||
if(bullets[expSlot].image){
|
||||
SPR_setDepth(bullets[expSlot].image, 5);
|
||||
SPR_setAnim(bullets[expSlot].image, 1);
|
||||
SPR_setFrame(bullets[expSlot].image, 0);
|
||||
SPR_setHFlip(bullets[expSlot].image, random() & 1);
|
||||
} else {
|
||||
bullets[expSlot].active = FALSE;
|
||||
}
|
||||
}
|
||||
// spawn big explosion at player position
|
||||
spawnExplosion(player.pos.x, player.pos.y, 3, TRUE); // yellow
|
||||
if(enemies[i].type != ENEMY_TYPE_BOSS){
|
||||
enemies[i].hp = 0;
|
||||
killEnemy(i);
|
||||
}
|
||||
if(!isAttract){
|
||||
player.lives--;
|
||||
if(player.lives == 0){
|
||||
gameOver = TRUE;
|
||||
XGM2_stop();
|
||||
sfxGameOver();
|
||||
} else {
|
||||
sfxPlayerHit();
|
||||
player.respawnClock = 120;
|
||||
SPR_setVisibility(player.image, HIDDEN);
|
||||
if(player.hasShield){
|
||||
// shield absorbs hit
|
||||
player.hasShield = FALSE;
|
||||
player.shieldClock = 0;
|
||||
removeShieldVisual();
|
||||
player.recoveringClock = 60;
|
||||
player.recoverFlash = TRUE;
|
||||
killBullets = TRUE;
|
||||
hitMessageClock = 120;
|
||||
hitMessageBullet = FALSE;
|
||||
} else {
|
||||
player.lives--;
|
||||
if(player.lives == 0){
|
||||
gameOver = TRUE;
|
||||
XGM2_stop();
|
||||
sfxGameOver();
|
||||
} else {
|
||||
sfxPlayerHit();
|
||||
levelPerfect = FALSE;
|
||||
player.respawnClock = 120;
|
||||
player.activePowerup = 0;
|
||||
player.powerupClock = 0;
|
||||
player.hasShield = FALSE;
|
||||
player.shieldClock = 0;
|
||||
removeShieldVisual();
|
||||
SPR_setVisibility(player.image, HIDDEN);
|
||||
killBullets = TRUE;
|
||||
hitMessageClock = 120;
|
||||
hitMessageBullet = FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -228,7 +366,7 @@ static void updateEnemy(u8 i){
|
|||
s16 sx = getScreenX(enemies[i].pos.x, player.camera);
|
||||
s16 sy = F32_toInt(enemies[i].pos.y);
|
||||
SPR_setVisibility(enemies[i].image, enemies[i].onScreen ? VISIBLE : HIDDEN);
|
||||
if(enemies[i].type != ENEMY_TYPE_DRONE && enemies[i].type != ENEMY_TYPE_BOSS)
|
||||
if(enemies[i].canFlipH)
|
||||
SPR_setHFlip(enemies[i].image, enemies[i].vel.x > 0);
|
||||
SPR_setPosition(enemies[i].image, sx - enemies[i].off, sy - enemies[i].off);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue