level design etc

This commit is contained in:
t. boddy 2026-02-17 17:40:34 -05:00
parent 0151304d05
commit 4036b5f07e
13 changed files with 460 additions and 21 deletions

BIN
flower.fur Normal file

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Before After
Before After

BIN
res/koakuma.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

View file

@ -21,6 +21,7 @@ SPRITE butterflySprite "butterfly.png" 4 4 NONE 8
SPRITE fairySprite "fairy2.png" 4 4 NONE 8
SPRITE aliceSprite "alice.png" 6 6 NONE 0
SPRITE humanSprite "human.png" 2 2 NONE 0
SPRITE koaSprite "koakuma.png" 4 4 NONE 0
// IMAGE mapPlayer "mapplayer.png" NONE NONE
IMAGE mapIndicator "mapindicator.png" NONE NONE

View file

@ -258,6 +258,19 @@ void updateChrome(){
SYS_hardReset();
return;
}
// level transition overlay
if(levelClearing){
if(levelClearClock == 2){
char lvlStr[4];
uintToStr(level + 2, lvlStr, 1);
VDP_drawText("LEVEL ", 15, 13);
VDP_drawText(lvlStr, 21, 13);
}
if(levelClearClock >= 110){
VDP_clearText(15, 13, 10);
}
return;
}
if(lastScore != score){
lastScore = score;
drawScore();

View file

@ -29,14 +29,16 @@ void spawnEnemy(u8 type, u8 zone){
// Calculate zone bounds (each zone is 512px)
fix32 zoneStart = FIX32(zone * 512);
fix32 randX, randY;
fix32 randX, randY, playerDist;
u16 attempts = 0;
do {
// Random X within zone: zoneStart + random(0-511)
randX = zoneStart + FIX32(random() % 512);
randY = FIX32(16 + (random() % 128));
attempts++;
} while(!isValidEnemyPosition(randX, randY) && attempts < 100);
playerDist = getWrappedDelta(randX, player.pos.x);
if(playerDist < 0) playerDist = -playerDist;
} while((playerDist < CULL_LIMIT || !isValidEnemyPosition(randX, randY)) && attempts < 100);
enemies[i].pos.x = randX;
enemies[i].pos.y = randY;
@ -47,13 +49,29 @@ void spawnEnemy(u8 type, u8 zone){
enemies[i].active = FALSE;
return;
}
enemies[i].hp = 1;
for(u8 j = 0; j < PROP_COUNT; j++){
enemies[i].ints[j] = 0;
}
switch(enemies[i].type){
case 0:
case ENEMY_TYPE_TEST:
loadEnemyOne(i);
break;
case ENEMY_TYPE_DRONE:
loadDrone(i);
break;
case ENEMY_TYPE_GUNNER:
loadGunner(i);
break;
case ENEMY_TYPE_HUNTER:
loadHunter(i);
break;
case ENEMY_TYPE_BUILDER:
loadBuilder(i);
break;
case ENEMY_TYPE_BOSS:
loadBoss(i);
break;
}
enemies[i].vel.x = fix32Mul(fix16ToFix32(cosFix16(enemies[i].angle)), enemies[i].speed);
enemies[i].vel.y = fix32Mul(fix16ToFix32(sinFix16(enemies[i].angle)), enemies[i].speed);
@ -61,7 +79,7 @@ void spawnEnemy(u8 type, u8 zone){
}
static void boundsEnemy(u8 i){
if(enemies[i].ints[3] >= 0){
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 the human was collected by player or gone, kill this enemy
if(!humans[h].active || humans[h].state == HUMAN_COLLECTED){
@ -75,7 +93,11 @@ static void boundsEnemy(u8 i){
if(humans[h].active) killHuman(h);
enemies[i].ints[3] = -1;
humanBeingCarried = FALSE;
// TODO: spawn mutant here
if(enemies[i].type == ENEMY_TYPE_BUILDER){
u8 zone = fix32ToInt(enemies[i].pos.x) / 512;
spawnEnemy(ENEMY_TYPE_GUNNER, zone);
}
enemies[i].hp = 0;
killEnemy(i);
return;
}
@ -104,9 +126,24 @@ static void updateEnemy(u8 i){
enemies[i].onScreen = (dx >= -CULL_LIMIT && dx <= CULL_LIMIT);
switch(enemies[i].type){
case 0:
case ENEMY_TYPE_TEST:
updateEnemyOne(i);
break;
case ENEMY_TYPE_DRONE:
updateDrone(i);
break;
case ENEMY_TYPE_GUNNER:
updateGunner(i);
break;
case ENEMY_TYPE_HUNTER:
updateHunter(i);
break;
case ENEMY_TYPE_BUILDER:
updateBuilder(i);
break;
case ENEMY_TYPE_BOSS:
updateBoss(i);
break;
}
s16 sx = getScreenX(enemies[i].pos.x, player.camera);

View file

@ -1,3 +1,4 @@
// test enemy -- for testing out bullet stress
void loadEnemyOne(u8 i){
enemies[i].ints[0] = random() % 60;
enemies[i].ints[2] = -1; // target human index
@ -5,7 +6,6 @@ void loadEnemyOne(u8 i){
enemies[i].angle = ((random() % 4) * 256) + 128;
enemies[i].speed = FIX32(2);
}
void updateEnemyOne(u8 i){
// carrying behavior: move upward, skip shooting
if(enemies[i].ints[3] >= 0){
@ -89,3 +89,247 @@ void updateEnemyOne(u8 i){
}
}
}
// --- Type 1: Drone ---
// Pressure enemy. Homes toward player, simple aimed shots.
// ints[0] = random shot offset, ints[1] = recalc timer
void loadDrone(u8 i){
enemies[i].ints[0] = random() % 60;
enemies[i].ints[1] = 0;
enemies[i].angle = random() % 1024;
enemies[i].speed = FIX32(2);
}
void updateDrone(u8 i){
// recalculate heading toward player every 30 frames
enemies[i].ints[1]++;
if(enemies[i].ints[1] >= 30){
enemies[i].ints[1] = 0;
fix32 dx = getWrappedDelta(player.pos.x, enemies[i].pos.x);
fix32 dy = player.pos.y - enemies[i].pos.y;
enemies[i].angle = honeAngle(
fix32ToFix16(enemies[i].pos.x), fix32ToFix16(enemies[i].pos.x) + fix32ToFix16(dx),
fix32ToFix16(enemies[i].pos.y), fix32ToFix16(enemies[i].pos.y) + fix32ToFix16(dy));
enemies[i].vel.x = fix32Mul(fix16ToFix32(cosFix16(enemies[i].angle)), enemies[i].speed);
enemies[i].vel.y = fix32Mul(fix16ToFix32(sinFix16(enemies[i].angle)), enemies[i].speed);
}
// shooting: 1 aimed bullet every ~40 frames (only if level >= 1 i.e. L2+)
if(level >= 1 && enemies[i].onScreen &&
enemies[i].clock % 40 == (u32)(enemies[i].ints[0]) % 40){
sfxEnemyShotC();
fix32 dx = getWrappedDelta(player.pos.x, enemies[i].pos.x);
fix32 dy = player.pos.y - enemies[i].pos.y;
s16 aimAngle = honeAngle(
fix32ToFix16(enemies[i].pos.x), fix32ToFix16(enemies[i].pos.x) + fix32ToFix16(dx),
fix32ToFix16(enemies[i].pos.y), fix32ToFix16(enemies[i].pos.y) + fix32ToFix16(dy));
struct bulletSpawner spawner = {
.x = enemies[i].pos.x,
.y = enemies[i].pos.y,
.anim = 3 + (random() % 9),
.speed = FIX32(4),
.angle = aimAngle,
};
spawnBullet(spawner, EMPTY);
}
}
// --- Type 2: Gunner ---
// Bullet geometry. Slow drift, patterned danmaku.
// ints[0] = pattern type (0=radial, 1=aimed fan), ints[1] = shot timer offset, ints[2] = angle accumulator
void loadGunner(u8 i){
enemies[i].ints[0] = random() % 2;
enemies[i].ints[1] = random() % 60;
enemies[i].ints[2] = 0;
enemies[i].angle = random() % 1024;
enemies[i].speed = FIX32(0.5);
}
void updateGunner(u8 i){
if(!enemies[i].onScreen) return;
if(enemies[i].ints[0] == 0){
// Pattern 0: Radial Burst - 8 bullets every ~60 frames
if(enemies[i].clock % 60 == (u32)(enemies[i].ints[1]) % 60){
sfxEnemyShotB();
s16 baseAngle = enemies[i].ints[2];
struct bulletSpawner spawner = {
.x = enemies[i].pos.x,
.y = enemies[i].pos.y,
.anim = 3 + (random() % 3),
.speed = FIX32(3),
.angle = baseAngle,
};
for(u8 j = 0; j < 8; j++){
spawnBullet(spawner, EMPTY);
spawner.angle += 128;
}
enemies[i].ints[2] += 24;
if(enemies[i].ints[2] >= 1024) enemies[i].ints[2] -= 1024;
}
} else {
// Pattern 1: Aimed Fan - 5 bullets spread +-64 every ~45 frames
if(enemies[i].clock % 45 == (u32)(enemies[i].ints[1]) % 45){
sfxEnemyShotA();
fix32 dx = getWrappedDelta(player.pos.x, enemies[i].pos.x);
fix32 dy = player.pos.y - enemies[i].pos.y;
s16 aimAngle = honeAngle(
fix32ToFix16(enemies[i].pos.x), fix32ToFix16(enemies[i].pos.x) + fix32ToFix16(dx),
fix32ToFix16(enemies[i].pos.y), fix32ToFix16(enemies[i].pos.y) + fix32ToFix16(dy));
struct bulletSpawner spawner = {
.x = enemies[i].pos.x,
.y = enemies[i].pos.y,
.anim = 6 + (random() % 3),
.speed = FIX32(3),
.angle = aimAngle - 64,
};
for(u8 j = 0; j < 5; j++){
spawnBullet(spawner, EMPTY);
spawner.angle += 32;
}
}
}
}
// --- Type 3: Hunter ---
// Fast chaser. Homes toward player every frame. No shooting.
void loadHunter(u8 i){
enemies[i].angle = random() % 1024;
enemies[i].speed = FIX32(5);
}
void updateHunter(u8 i){
fix32 dx = getWrappedDelta(player.pos.x, enemies[i].pos.x);
fix32 dy = player.pos.y - enemies[i].pos.y;
enemies[i].angle = honeAngle(
fix32ToFix16(enemies[i].pos.x), fix32ToFix16(enemies[i].pos.x) + fix32ToFix16(dx),
fix32ToFix16(enemies[i].pos.y), fix32ToFix16(enemies[i].pos.y) + fix32ToFix16(dy));
enemies[i].vel.x = fix32Mul(fix16ToFix32(cosFix16(enemies[i].angle)), enemies[i].speed);
enemies[i].vel.y = fix32Mul(fix16ToFix32(sinFix16(enemies[i].angle)), enemies[i].speed);
}
// --- Type 4: Builder (Abductor) ---
// Seeks and abducts humans. On reaching top with human, spawns a Gunner.
// ints[0] = scan offset, ints[2] = target human, ints[3] = carried human
void loadBuilder(u8 i){
enemies[i].ints[0] = random() % 60;
enemies[i].ints[2] = -1;
enemies[i].ints[3] = -1;
enemies[i].angle = random() % 1024;
enemies[i].speed = FIX32(0.7);
}
void updateBuilder(u8 i){
// carrying: steer upward
if(enemies[i].ints[3] >= 0){
enemies[i].angle = 704 + (random() % 128);
enemies[i].speed = FIX32(1.4);
enemies[i].vel.x = fix32Mul(fix16ToFix32(cosFix16(enemies[i].angle)), enemies[i].speed);
enemies[i].vel.y = fix32Mul(fix16ToFix32(sinFix16(enemies[i].angle)), enemies[i].speed);
return;
}
// cancel target if a human is already being carried
if(humanBeingCarried && enemies[i].ints[2] >= 0){
enemies[i].ints[2] = -1;
}
// scan for nearest walking human every 30 frames
if(!humanBeingCarried && enemies[i].clock % 30 == (u32)(enemies[i].ints[0]) % 30){
s16 bestHuman = -1;
fix32 bestDist = FIX32(9999);
for(s16 j = 0; j < HUMAN_COUNT; j++){
if(!humans[j].active || humans[j].state != HUMAN_WALKING) continue;
fix32 dx = getWrappedDelta(enemies[i].pos.x, humans[j].pos.x);
fix32 dy = enemies[i].pos.y - humans[j].pos.y;
fix32 dist = (dx < 0 ? -dx : dx) + (dy < 0 ? -dy : dy);
if(dist < bestDist && dist < FIX32(256)){
bestDist = dist;
bestHuman = j;
}
}
enemies[i].ints[2] = bestHuman;
}
// steer toward target human
if(enemies[i].ints[2] >= 0){
s16 t = enemies[i].ints[2];
if(!humans[t].active || humans[t].state != HUMAN_WALKING){
enemies[i].ints[2] = -1;
} else {
fix32 dx = getWrappedDelta(humans[t].pos.x, enemies[i].pos.x);
fix32 dy = humans[t].pos.y - enemies[i].pos.y;
enemies[i].speed = FIX32(1.4);
s16 angle = honeAngle(
fix32ToFix16(enemies[i].pos.x), fix32ToFix16(humans[t].pos.x),
fix32ToFix16(enemies[i].pos.y), fix32ToFix16(humans[t].pos.y));
enemies[i].vel.x = fix32Mul(fix16ToFix32(cosFix16(angle)), enemies[i].speed);
enemies[i].vel.y = fix32Mul(fix16ToFix32(sinFix16(angle)), enemies[i].speed);
// grab check
fix32 adx = dx < 0 ? -dx : dx;
fix32 ady = dy < 0 ? -dy : dy;
if(adx < FIX32(16) && ady < FIX32(16)){
enemies[i].ints[3] = t;
enemies[i].ints[2] = -1;
humanBeingCarried = TRUE;
humans[t].state = HUMAN_CARRIED;
humans[t].carriedBy = i;
}
}
}
}
// --- Type 5: Boss ---
// High HP, alternates 2 patterns. hp set by level data via ints[0].
// ints[0] = initial hp (set by stage), ints[1] = pattern timer, ints[2] = current pattern
void loadBoss(u8 i){
enemies[i].hp = pendingBossHp > 0 ? pendingBossHp : 10;
pendingBossHp = 0;
enemies[i].ints[1] = 0;
enemies[i].ints[2] = 0;
enemies[i].angle = random() % 1024;
enemies[i].speed = FIX32(1);
}
void updateBoss(u8 i){
if(!enemies[i].onScreen) return;
enemies[i].ints[1]++;
// alternate patterns every 180 frames
if(enemies[i].ints[1] >= 180){
enemies[i].ints[1] = 0;
enemies[i].ints[2] = 1 - enemies[i].ints[2];
}
if(enemies[i].ints[2] == 0){
// Pattern A: Radial burst - 12 bullets every 50 frames
if(enemies[i].ints[1] % 50 == 0){
sfxEnemyShotB();
s16 baseAngle = random() % 1024;
struct bulletSpawner spawner = {
.x = enemies[i].pos.x,
.y = enemies[i].pos.y,
.anim = 3 + (random() % 3),
.speed = FIX32(3),
.angle = baseAngle,
};
for(u8 j = 0; j < 12; j++){
spawnBullet(spawner, EMPTY);
spawner.angle += 85;
}
}
} else {
// Pattern B: Aimed wide fan - 8 bullets every 40 frames
if(enemies[i].ints[1] % 40 == 0){
sfxEnemyShotA();
fix32 dx = getWrappedDelta(player.pos.x, enemies[i].pos.x);
fix32 dy = player.pos.y - enemies[i].pos.y;
s16 aimAngle = honeAngle(
fix32ToFix16(enemies[i].pos.x), fix32ToFix16(enemies[i].pos.x) + fix32ToFix16(dx),
fix32ToFix16(enemies[i].pos.y), fix32ToFix16(enemies[i].pos.y) + fix32ToFix16(dy));
struct bulletSpawner spawner = {
.x = enemies[i].pos.x,
.y = enemies[i].pos.y,
.anim = 9 + (random() % 3),
.speed = FIX32(3),
.angle = aimAngle - 112,
};
for(u8 j = 0; j < 8; j++){
spawnBullet(spawner, EMPTY);
spawner.angle += 32;
}
}
}
}

View file

@ -1,6 +1,6 @@
u32 clock;
#define CLOCK_LIMIT 32000
#define PROP_COUNT 4
#define PROP_COUNT 8
#define GAME_H_F FIX32(224)
@ -27,6 +27,11 @@ bool started;
bool gameOver;
bool paused, isPausing;
s16 enemyCount, bulletCount;
u8 level;
s16 pendingBossHp;
bool waitForRelease;
bool levelClearing;
u32 levelClearClock;
// controls
struct controls {
@ -81,11 +86,19 @@ struct bullet bullets[BULLET_COUNT];
// enemies
#define ENEMY_COUNT 16
#define ENEMY_COUNT 24
#define ENEMY_TYPE_TEST 0
#define ENEMY_TYPE_DRONE 1
#define ENEMY_TYPE_GUNNER 2
#define ENEMY_TYPE_HUNTER 3
#define ENEMY_TYPE_BUILDER 4
#define ENEMY_TYPE_BOSS 5
struct enemy {
bool active, onScreen;
u8 type;
s16 hp;
s16 angle, off;
u32 clock;
fix32 speed;
@ -151,6 +164,8 @@ void killBullet(u8 i, bool explode){
}
void killEnemy(u8 i){
enemies[i].hp--;
if(enemies[i].hp > 0) return;
if(enemies[i].ints[3] >= 0){
s16 h = enemies[i].ints[3];
if(humans[h].active){

View file

@ -105,7 +105,7 @@ static void updateHuman(u8 i){
fix32 dx = getWrappedDelta(humans[i].pos.x, player.pos.x);
if(humans[i].state != HUMAN_CARRIED && humans[i].state != HUMAN_COLLECTED){
fix32 dy = humans[i].pos.y - player.pos.y;
if(dx >= FIX32(-24) && dx <= FIX32(24) && dy >= FIX32(-24) && dy <= FIX32(24)){
if(dx >= FIX32(-32) && dx <= FIX32(32) && dy >= FIX32(-32) && dy <= FIX32(32)){
score += (humans[i].state == HUMAN_FALLING) ? 2000 : 1000;
sfxPickup();
humans[i].state = HUMAN_COLLECTED;

View file

@ -23,22 +23,58 @@ static void loadInternals(){
VDP_setTextPriority(1);
}
void clearLevel(){
for(s16 i = 0; i < BULLET_COUNT; i++)
if(bullets[i].active) killBullet(i, FALSE);
for(s16 i = 0; i < ENEMY_COUNT; i++)
if(enemies[i].active){ enemies[i].hp = 0; killEnemy(i); }
for(s16 i = 0; i < HUMAN_COUNT; i++)
if(humans[i].active) killHuman(i);
humanBeingCarried = FALSE;
collectedCount = 0;
// black out everything
SPR_setVisibility(player.image, HIDDEN);
VDP_clearTileMapRect(BG_A, 0, 0, 128, 32);
VDP_clearTileMapRect(BG_B, 0, 0, 128, 32);
}
void loadGame(){
loadBackground();
loadPlayer();
loadChrome();
loadStage();
loadLevel(0);
started = TRUE;
}
static void updateGame(){
updateChrome();
updateSfx();
if(levelClearing){
levelClearClock++;
if(levelClearClock == 1){
clearLevel();
}
if(levelClearClock >= 120){
levelClearing = FALSE;
loadBackground();
loadChrome();
loadLevel(level + 1);
SPR_setVisibility(player.image, VISIBLE);
}
return;
}
if(!paused){
updatePlayer();
if(clock % 2 == 0){
updateEnemies();
if(!gameOver && enemyCount == 0) gameOver = TRUE;
if(!gameOver && enemyCount == 0){
if(level >= LEVEL_COUNT - 1){
gameOver = TRUE;
} else {
levelClearing = TRUE;
levelClearClock = 0;
}
}
updateHumans();
} else {
updateBackground();

View file

@ -130,6 +130,11 @@ void loadPlayer(){
}
void updatePlayer(){
if(waitForRelease){
if(!ctrl.a && !ctrl.b && !ctrl.c && !ctrl.start)
waitForRelease = FALSE;
return;
}
if(!gameOver){
if(player.recoveringClock > 0){
if(player.recoveringClock % 10 == 1)

View file

@ -1,15 +1,102 @@
void loadStage(){
// Spawn 3 enemies per zone (4 zones = 12 total)
for(u8 zone = 0; zone < 4; zone++){
for(u8 i = 0; i < 3; i++){
spawnEnemy(0, zone);
struct LevelDef {
u8 drones, gunners, hunters, builders;
u8 bossHp;
u8 humans;
u8 gunnerPattern; // 0=radial, 1=aimed fan, 2=mix
bool dronesShoot;
};
// dr gn hn bl boss hum pat shoot
const struct LevelDef levels[30] = {
// Phase 1: "Immediate danger" (L1-L6)
{ 8, 1, 0, 0, 0, 8, 0, FALSE }, // L1
{ 10, 2, 0, 0, 0, 8, 0, TRUE }, // L2
{ 12, 2, 0, 0, 0, 8, 0, TRUE }, // L3
{ 10, 3, 0, 0, 0, 8, 1, TRUE }, // L4
{ 14, 3, 0, 0, 0, 8, 1, TRUE }, // L5
{ 8, 0, 0, 0, 8, 8, 0, TRUE }, // L6 BOSS
// Phase 2: "You can't save everything" (L7-L12)
{ 10, 0, 0, 1, 0, 8, 0, TRUE }, // L7
{ 10, 2, 0, 1, 0, 8, 0, TRUE }, // L8
{ 12, 0, 0, 2, 0, 8, 0, TRUE }, // L9
{ 14, 3, 0, 1, 0, 8, 1, TRUE }, // L10 WALL
{ 10, 2, 0, 2, 0, 8, 2, TRUE }, // L11
{ 8, 0, 0, 1, 12, 8, 0, TRUE }, // L12 BOSS
// Phase 3: "Geometry matters" (L13-L18)
{ 8, 0, 4, 0, 0, 8, 0, TRUE }, // L13
{ 8, 3, 2, 0, 0, 8, 1, TRUE }, // L14
{ 16, 0, 0, 0, 0, 8, 0, TRUE }, // L15 FARM
{ 10, 2, 4, 0, 0, 8, 2, TRUE }, // L16
{ 12, 3, 3, 0, 0, 8, 1, TRUE }, // L17
{ 0, 2, 2, 0, 15, 8, 2, TRUE }, // L18 BOSS
// Phase 4: "Suffocation" (L19-L24)
{ 12, 4, 0, 0, 0, 8, 2, TRUE }, // L19
{ 14, 4, 0, 2, 0, 8, 2, TRUE }, // L20 WALL
{ 10, 0, 6, 0, 0, 8, 0, TRUE }, // L21
{ 12, 4, 2, 0, 0, 8, 1, TRUE }, // L22
{ 14, 4, 0, 2, 0, 8, 2, TRUE }, // L23
{ 0, 3, 0, 1, 20, 8, 2, TRUE }, // L24 BOSS
// Phase 5: "Arcade cruelty" (L25-L30)
{ 16, 0, 4, 0, 0, 8, 0, TRUE }, // L25
{ 12, 6, 0, 0, 0, 8, 2, TRUE }, // L26
{ 14, 2, 4, 0, 0, 8, 1, TRUE }, // L27
{ 16, 4, 0, 2, 0, 8, 2, TRUE }, // L28
{ 6, 2, 2, 1, 10, 8, 2, TRUE }, // L29 MINI-BOSS
{ 4, 2, 2, 1, 30, 8, 2, TRUE }, // L30 FINAL
};
#define LEVEL_COUNT 30
static void distributeEnemies(u8 type, u8 count){
for(u8 i = 0; i < count; i++){
u8 zone = i % 4;
spawnEnemy(type, zone);
}
}
void loadLevel(u8 lvl){
if(lvl >= LEVEL_COUNT) lvl = LEVEL_COUNT - 1;
level = lvl;
const struct LevelDef* def = &levels[lvl];
distributeEnemies(ENEMY_TYPE_DRONE, def->drones);
distributeEnemies(ENEMY_TYPE_GUNNER, def->gunners);
distributeEnemies(ENEMY_TYPE_HUNTER, def->hunters);
distributeEnemies(ENEMY_TYPE_BUILDER, def->builders);
// set gunner pattern based on level def
for(s16 i = 0; i < ENEMY_COUNT; i++){
if(enemies[i].active && enemies[i].type == ENEMY_TYPE_GUNNER){
if(def->gunnerPattern == 2)
enemies[i].ints[0] = random() % 2;
else
enemies[i].ints[0] = def->gunnerPattern;
}
}
// Spawn 2 humans per zone (4 zones = 8 total)
for(u8 zone = 0; zone < 4; zone++){
for(u8 i = 0; i < 2; i++){
if(def->bossHp > 0){
pendingBossHp = def->bossHp;
spawnEnemy(ENEMY_TYPE_BOSS, 1);
}
// spawn humans
u8 humansToSpawn = def->humans;
for(u8 zone = 0; zone < 4 && humansToSpawn > 0; zone++){
u8 perZone = humansToSpawn >= 4 ? 2 : 1;
for(u8 h = 0; h < perZone && humansToSpawn > 0; h++){
spawnHuman(zone);
humansToSpawn--;
}
}
loadMap();
}
}
// legacy test stage
void loadStage(){
loadLevel(0);
}

View file

@ -9,6 +9,7 @@ void loadStart(){
void updateStart(){
if(ctrl.a || ctrl.b || ctrl.c || ctrl.start){
VDP_clearTileMapRect(BG_A, 0, 0, 40, 28);
waitForRelease = TRUE;
loadGame();
}
}