feat(enemy): els enemics poden disparar bales via tick d'IA

This commit is contained in:
2026-05-25 20:05:01 +02:00
parent 5ad433e63a
commit 14295ce859
18 changed files with 361 additions and 92 deletions
+59 -4
View File
@@ -41,20 +41,32 @@ namespace Systems::Collision {
void detectBulletEnemy(Context& ctx) {
constexpr float AMPLIFIER = Defaults::Game::COLLISION_BULLET_ENEMY_AMPLIFIER;
constexpr uint8_t NO_SHOOTER = 0xFF;
constexpr uint8_t ENEMY_BASE = Defaults::Entities::ENEMY_OWNER_BASE;
for (auto& bullet : ctx.bullets) {
if (!bullet.isActive()) {
continue;
}
for (auto& enemy : ctx.enemies) {
const uint8_t BULLET_OWNER = bullet.getOwnerId();
const bool IS_ENEMY_BULLET = BULLET_OWNER >= ENEMY_BASE;
for (std::size_t i = 0; i < ctx.enemies.size(); ++i) {
Enemy& enemy = ctx.enemies[i];
// Self-shot: una bala d'enemic mai no impacta el seu propi creador.
if (IS_ENEMY_BULLET && BULLET_OWNER == static_cast<uint8_t>(ENEMY_BASE + i)) {
continue;
}
if (!Physics::checkCollisionSwept(bullet.getPrevPosition(), bullet.getCenter(), bullet.getCollisionRadius(), enemy, AMPLIFIER)) {
continue;
}
// *** COLISIÓN bullet → enemy ***
// La cadena d'efectes (impulse, hurt, destroy, debris, score...) viu
// al YAML de l'enemic via la secció `events:`. Aquí només dispatchem.
Systems::EnemyEvents::dispatchEvent(ctx, enemy, EnemyEventType::ON_HIT, bullet.getOwnerId(), &bullet);
// al YAML de l'enemic via la secció `events:`. Si la bala és d'enemic,
// no atribuïm el kill a cap player (NO_SHOOTER).
const uint8_t SHOOTER = IS_ENEMY_BULLET ? NO_SHOOTER : BULLET_OWNER;
Systems::EnemyEvents::dispatchEvent(ctx, enemy, EnemyEventType::ON_HIT, SHOOTER, &bullet);
breakBullet(ctx.debris_manager, bullet);
break;
}
@@ -200,16 +212,59 @@ namespace Systems::Collision {
}
}
void detectEnemyBulletShip(Context& ctx) {
constexpr float AMPLIFIER = Defaults::Game::COLLISION_BULLET_PLAYER_AMPLIFIER;
constexpr uint8_t ENEMY_BASE = Defaults::Entities::ENEMY_OWNER_BASE;
for (auto& bullet : ctx.bullets) {
if (!bullet.isActive() || bullet.getOwnerId() < ENEMY_BASE) {
continue;
}
for (uint8_t player_id = 0; player_id < 2; player_id++) {
if (ctx.hit_timer_per_player[player_id] > 0.0F ||
!ctx.ships[player_id].isActive() ||
ctx.ships[player_id].isInvulnerable()) {
continue;
}
const bool JUGADOR_ACTIU = (player_id == 0)
? ctx.match_config.player1_active
: ctx.match_config.player2_active;
if (!JUGADOR_ACTIU) {
continue;
}
if (!Physics::checkCollisionSwept(bullet.getPrevPosition(), bullet.getCenter(), bullet.getCollisionRadius(), ctx.ships[player_id], AMPLIFIER)) {
continue;
}
// *** BALA D'ENEMIC → SHIP ***
// Apliquem l'impuls de la bala abans del hurt/death perquè la
// velocitat de la nau quedi capturada per als debris.
const Vec2 IMPULSE = bullet.getBody().velocity *
(bullet.getBody().mass * bullet.getConfig().physics.impact_momentum_factor);
ctx.ships[player_id].getBody().applyImpulse(IMPULSE);
if (ctx.ships[player_id].isHurt()) {
// Segon impacte durant HURT → mort.
ctx.on_player_hit(player_id);
} else {
ctx.ships[player_id].hurt();
}
breakBullet(ctx.debris_manager, bullet);
break; // una bala impacta una vegada per frame
}
}
}
void detectAll(Context& ctx) {
processWoundedDeaths(ctx); // expiran ANTES de ser tocadas por bala este frame
detectBulletEnemy(ctx);
detectWoundedChain(ctx); // un herit pot ferir a un sa al fregar-lo
detectShipEnemy(ctx);
detectBulletPlayer(ctx);
detectEnemyBulletShip(ctx);
}
void desactivateOutOfBoundsBullets(
std::array<Bullet, static_cast<std::size_t>(Defaults::Entities::MAX_BULLETS) * 2>& bullets,
std::array<Bullet, static_cast<std::size_t>(Defaults::Entities::MAX_BULLETS_TOTAL)>& bullets,
Effects::DebrisManager& debris_manager) {
float min_x;
float max_x;