// enemy_event_dispatcher.cpp - Implementació del dispatcher d'events d'enemic // © 2026 JailDesigner #include "game/systems/enemy_event_dispatcher.hpp" #include #include "core/defaults.hpp" #include "core/types.hpp" #include "game/entities/bullet.hpp" #include "game/entities/bullet_config.hpp" #include "game/entities/enemy_config.hpp" namespace Systems::EnemyEvents { namespace { constexpr uint8_t NO_SHOOTER = 0xFF; void doAddScore(Systems::Collision::Context& ctx, const Enemy& enemy, uint8_t shooter) { const int POINTS = enemy.getConfig().score; if (shooter != NO_SHOOTER) { ctx.score_per_player[shooter] += POINTS; } ctx.floating_score_manager.crear(POINTS, enemy.getCenter()); } // Helper compartit per CREATE_DEBRIS i CREATE_DEBRIS_PARTIAL: única // crida a explode(), paràmetres alineats; només canvia piece_scale // (1.0 = explosió completa, 0.3 = xip d'esquerda). void spawnDebrisForEnemy(Systems::Collision::Context& ctx, const Enemy& enemy, const Bullet* bullet, float piece_scale) { constexpr float SPEED_EXPLOSIO = 80.0F; const Vec2 INHERITED_VEL = enemy.getVelocityVector() * Defaults::Physics::Debris::ENEMY_VELOCITY_INHERITANCE; const Vec2 BULLET_VEL = (bullet != nullptr) ? bullet->getBody().velocity : Vec2{}; ctx.debris_manager.explode( enemy.getShape(), enemy.getCenter(), 0.0F, 1.0F, SPEED_EXPLOSIO, enemy.getBrightness(), INHERITED_VEL, 0.0F, 0.0F, Defaults::Sound::EXPLOSION, enemy.getConfig().colors.normal, Defaults::Physics::Debris::ENEMY_LIFETIME, Defaults::Physics::Debris::ENEMY_FRICTION, Defaults::Physics::Debris::ENEMY_SEGMENT_MULTIPLIER, BULLET_VEL, piece_scale); } // Helper compartit per CREATE_FIREWORKS i CREATE_FIREWORKS_SMALL: // mateixa crida a spawn(), només canvien n_points, initial_speed i // glow_color segons el "tamany" del burst (mort vs feedback per hit). void spawnFireworksForEnemy(Systems::Collision::Context& ctx, const Enemy& enemy, int n_points, float initial_speed, SDL_Color glow_color) { ctx.firework_manager.spawn(enemy.getCenter(), Defaults::FX::Firework::DEFAULT_COLOR, initial_speed, n_points, Defaults::FX::Firework::INITIAL_BRIGHTNESS, /*glow=*/true, glow_color); } void doApplyImpulse(Enemy& enemy, const Bullet* bullet) { if (bullet == nullptr) { return; } const Vec2 IMPULSE = bullet->getBody().velocity * (bullet->getBody().mass * bullet->getConfig().physics.impact_momentum_factor); enemy.applyImpulse(IMPULSE); } } // namespace void dispatchEvent(Systems::Collision::Context& ctx, Enemy& enemy, EnemyEventType event, uint8_t shooter_id, const Bullet* bullet) { const auto& actions = enemy.getConfig().events.getActions(event); // Pre-scan: aquest event matarà l'enemic? Si sí, l'impuls de la bala // va directament als debris (via doCreateDebris) i NO s'aplica al cos // — així evitem el "double-count" on els trossos hereten la velocitat // del cos (boostat per la bala) I a més el seu propi impuls de bala. // Regla: el bullet impacta al cos O als trossos, mai a tots dos. bool will_die = false; for (const auto& action : actions) { if (action.type == EnemyActionType::DESTROY) { will_die = true; break; } if (action.type == EnemyActionType::SET_HURT && enemy.isWounded()) { will_die = true; break; } } for (const auto& action : actions) { // Si una acció prèvia d'aquest chain ha destruït l'enemic // (típicament DECREASE_HEALTH→ON_NO_HEALTH→SET_HURT-wounded→DESTROY), // saltem la resta — no té sentit aplicar APPLY_IMPULSE o FLASH a un // cos ja inactiu. if (!enemy.isActive()) { break; } switch (action.type) { case EnemyActionType::SET_HURT: if (enemy.isWounded()) { // Segon hit sobre wounded → mort immediata (regla 2-hits). dispatchEvent(ctx, enemy, EnemyEventType::ON_DESTROY, shooter_id, bullet); enemy.destroy(); } else { enemy.hurt(shooter_id); } break; case EnemyActionType::DESTROY: dispatchEvent(ctx, enemy, EnemyEventType::ON_DESTROY, shooter_id, bullet); enemy.destroy(); break; case EnemyActionType::ADD_SCORE: doAddScore(ctx, enemy, shooter_id); break; case EnemyActionType::CREATE_DEBRIS: spawnDebrisForEnemy(ctx, enemy, bullet, 1.0F); break; case EnemyActionType::CREATE_DEBRIS_PARTIAL: spawnDebrisForEnemy(ctx, enemy, bullet, Defaults::Enemies::Debris::PARTIAL_PIECE_SCALE); break; case EnemyActionType::CREATE_FIREWORKS: // Burst de mort: glow amb el color wounded (daurat) per // marcar la mort com a esdeveniment "calent". spawnFireworksForEnemy(ctx, enemy, Defaults::FX::Firework::N_POINTS, Defaults::FX::Firework::SPEED, enemy.getConfig().colors.wounded); break; case EnemyActionType::CREATE_FIREWORKS_SMALL: // Burst d'impacte: glow amb el color de l'enemic, perquè // l'espurna llegisca com a "tros del propi cos saltant". spawnFireworksForEnemy(ctx, enemy, Defaults::Enemies::Fireworks::SMALL_N_POINTS, Defaults::Enemies::Fireworks::SMALL_SPEED, enemy.getConfig().colors.normal); break; case EnemyActionType::APPLY_IMPULSE: if (!will_die) { doApplyImpulse(enemy, bullet); } break; case EnemyActionType::DECREASE_HEALTH: enemy.decrementHealth(shooter_id); if (enemy.getHealth() <= 0) { dispatchEvent(ctx, enemy, EnemyEventType::ON_NO_HEALTH, shooter_id, bullet); } break; case EnemyActionType::FLASH: enemy.triggerFlash(); break; } } } } // namespace Systems::EnemyEvents