// collision_system.cpp - Implementación del sistema de colisiones #include "game/systems/collision_system.hpp" #include #include "core/audio/audio.hpp" #include "core/physics/collision.hpp" #include "core/types.hpp" namespace Systems::Collision { void detectBulletEnemy(Context& ctx) { constexpr float AMPLIFIER = Defaults::Game::COLLISION_BULLET_ENEMY_AMPLIFIER; constexpr float VELOCITAT_EXPLOSIO = 80.0F; // px/s (explosión suau) for (auto& bullet : ctx.bullets) { for (auto& enemy : ctx.enemies) { if (!Physics::check_collision(bullet, enemy, AMPLIFIER)) { continue; } // *** COLISIÓN bullet → enemy *** const Vec2& POS_ENEMIC = enemy.getCenter(); // 1. Puntos según tipo int points = 0; switch (enemy.getType()) { case EnemyType::PENTAGON: points = Defaults::Enemies::Scoring::PENTAGON_SCORE; break; case EnemyType::QUADRAT: points = Defaults::Enemies::Scoring::QUADRAT_SCORE; break; case EnemyType::MOLINILLO: points = Defaults::Enemies::Scoring::MOLINILLO_SCORE; break; } uint8_t owner_id = bullet.getOwnerId(); ctx.score_per_player[owner_id] += points; ctx.floating_score_manager.crear(points, POS_ENEMIC); // 2. Destruir enemy + crear explosión (debris hereda color del enemy) SDL_Color enemy_color{}; switch (enemy.getType()) { case EnemyType::PENTAGON: enemy_color = Defaults::Palette::PENTAGON; break; case EnemyType::QUADRAT: enemy_color = Defaults::Palette::QUADRAT; break; case EnemyType::MOLINILLO: enemy_color = Defaults::Palette::MOLINILLO; break; } enemy.destruir(); Vec2 vel_enemic = enemy.getVelocityVector(); ctx.debris_manager.explode( enemy.getShape(), POS_ENEMIC, 0.0F, // angle (la rotación es interna del enemy) 1.0F, // escala VELOCITAT_EXPLOSIO, enemy.getBrightness(), vel_enemic, enemy.getRotationDelta(), 0.0F, // sin herencia visual Defaults::Sound::EXPLOSION, enemy_color ); // 3. Desactivar bullet (solo destruye 1 enemy) bullet.desactivar(); break; } } } void detectShipEnemy(Context& ctx) { constexpr float AMPLIFIER = Defaults::Game::COLLISION_SHIP_ENEMY_AMPLIFIER; for (uint8_t i = 0; i < 2; i++) { // Skip si ya tocado / muerto / invulnerable if (ctx.hit_timer_per_player[i] > 0.0F || !ctx.ships[i].isAlive() || ctx.ships[i].isInvulnerable()) { continue; } for (const auto& enemy : ctx.enemies) { if (enemy.isInvulnerable()) { continue; } if (Physics::check_collision(ctx.ships[i], enemy, AMPLIFIER)) { ctx.on_player_hit(i); break; // Solo una colisión por player por frame } } } } void detectBulletPlayer(Context& ctx) { if (!Defaults::Game::FRIENDLY_FIRE_ENABLED) { return; } constexpr float AMPLIFIER = Defaults::Game::COLLISION_BULLET_PLAYER_AMPLIFIER; for (auto& bullet : ctx.bullets) { if (!bullet.esta_activa() || bullet.getGraceTimer() > 0.0F) { continue; } const uint8_t BULLET_OWNER = bullet.getOwnerId(); 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].isAlive() || ctx.ships[player_id].isInvulnerable()) { continue; } const bool JUGADOR_ACTIU = (player_id == 0) ? ctx.match_config.jugador1_actiu : ctx.match_config.jugador2_actiu; if (!JUGADOR_ACTIU) { continue; } if (!Physics::check_collision(bullet, ctx.ships[player_id], AMPLIFIER)) { continue; } // *** FRIENDLY FIRE HIT *** if (BULLET_OWNER == player_id) { // Self-hit: víctima pierde 1 vida. ctx.on_player_hit(player_id); } else { // Teammate hit: víctima pierde 1, atacante gana 1. ctx.on_player_hit(player_id); ctx.lives_per_player[BULLET_OWNER]++; } Audio::get()->playSound(Defaults::Sound::FRIENDLY_FIRE_HIT, Audio::Group::GAME); bullet.desactivar(); break; // Una bullet solo impacta una vez por frame } } } void detectAll(Context& ctx) { detectBulletEnemy(ctx); detectShipEnemy(ctx); detectBulletPlayer(ctx); } } // namespace Systems::Collision