#define HUMAN_OFF 16 void spawnHuman(u8 zone){ s16 i = -1; for(s16 j = 0; j < HUMAN_COUNT; j++) if(!humans[j].active) { i = j; break; } if(i == -1) return; humans[i].active = TRUE; humans[i].state = HUMAN_WALKING; humans[i].carriedBy = -1; fix32 zoneStart = FIX32(zone * 512); humans[i].pos.x = zoneStart + FIX32(random() % 512); humans[i].pos.y = GAME_H_F - FIX32(24); fix32 speeds[] = { FIX32(0.3), FIX32(0.4), FIX32(0.5) }; humans[i].vel.x = (random() % 2 == 0) ? speeds[random() % 3] : -speeds[random() % 3]; humans[i].vel.y = (random() % 2 == 0) ? FIX32(0.1) : FIX32(-0.1); humans[i].image = SPR_addSprite(&koaSprite, getScreenX(humans[i].pos.x, player.camera) - HUMAN_OFF, fix32ToInt(humans[i].pos.y) - HUMAN_OFF, TILE_ATTR(PAL0, 0, 0, 0)); if(!humans[i].image){ humans[i].active = FALSE; return; } } static void updateHuman(u8 i){ switch(humans[i].state){ case HUMAN_WALKING: // Y bounce: bob 4px around ground level if(humans[i].pos.y >= GAME_H_F - FIX32(20) || humans[i].pos.y <= GAME_H_F - FIX32(28)) humans[i].vel.y *= -1; // X wrap if(humans[i].pos.x >= GAME_WRAP) humans[i].pos.x -= GAME_WRAP; if(humans[i].pos.x < 0) humans[i].pos.x += GAME_WRAP; humans[i].pos.x += humans[i].vel.x; humans[i].pos.y += humans[i].vel.y; break; case HUMAN_CARRIED: // follow carrier enemy position if(humans[i].carriedBy >= 0 && enemies[humans[i].carriedBy].active){ humans[i].pos.x = enemies[humans[i].carriedBy].pos.x; humans[i].pos.y = enemies[humans[i].carriedBy].pos.y + FIX32(16); } else { // carrier died (shouldn't normally reach here, killEnemy handles it) humans[i].state = HUMAN_FALLING; humans[i].carriedBy = -1; humans[i].vel.x = 0; humans[i].vel.y = FIX32(3); humanBeingCarried = FALSE; } break; case HUMAN_FALLING: humans[i].pos.y += humans[i].vel.y; // land on ground if(humans[i].pos.y >= GAME_H_F - FIX32(24)){ humans[i].pos.y = GAME_H_F - FIX32(24); humans[i].state = HUMAN_WALKING; fix32 speeds[] = { FIX32(0.3), FIX32(0.4), FIX32(0.5) }; humans[i].vel.x = (random() % 2 == 0) ? speeds[random() % 3] : -speeds[random() % 3]; humans[i].vel.y = (random() % 2 == 0) ? FIX32(0.1) : FIX32(-0.1); } break; case HUMAN_COLLECTED: { fix32 targetX, targetY; if(humans[i].trailIndex == 0){ targetX = player.pos.x; targetY = player.pos.y + FIX32(24); } else { // find the human ahead in the chain targetX = player.pos.x; targetY = player.pos.y + FIX32(24); for(s16 j = 0; j < HUMAN_COUNT; j++){ if(humans[j].active && humans[j].state == HUMAN_COLLECTED && humans[j].trailIndex == humans[i].trailIndex - 1){ targetX = humans[j].pos.x; targetY = humans[j].pos.y + FIX32(8); break; } } } fix32 deltaX = getWrappedDelta(targetX, humans[i].pos.x); humans[i].pos.x += deltaX >> 2; humans[i].pos.y += (targetY - humans[i].pos.y) >> 2; // X wrap if(humans[i].pos.x >= GAME_WRAP) humans[i].pos.x -= GAME_WRAP; if(humans[i].pos.x < 0) humans[i].pos.x += GAME_WRAP; break; } } // collect: check overlap with player (walking or falling only) 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)){ score += (humans[i].state == HUMAN_FALLING) ? 2000 : 1000; sfxPickup(); humans[i].state = HUMAN_COLLECTED; humans[i].trailIndex = collectedCount++; return; } } s16 sx = getScreenX(humans[i].pos.x, player.camera); s16 sy = fix32ToInt(humans[i].pos.y); bool visible = (dx >= -CULL_LIMIT && dx <= CULL_LIMIT); SPR_setVisibility(humans[i].image, visible ? VISIBLE : HIDDEN); SPR_setPosition(humans[i].image, sx - HUMAN_OFF, sy - HUMAN_OFF); } void updateHumans(){ for(s16 i = 0; i < HUMAN_COUNT; i++) if(humans[i].active) updateHuman(i); }