diff --git a/CMakeLists.txt b/CMakeLists.txt index f8b2d53..877859c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ # CMakeLists.txt cmake_minimum_required(VERSION 3.10) -project(orni VERSION 0.6.0) +project(orni VERSION 0.7.0) # Info del proyecto set(PROJECT_LONG_NAME "Orni Attack") diff --git a/source/core/defaults.hpp b/source/core/defaults.hpp index 3b6f2fc..b5f8784 100644 --- a/source/core/defaults.hpp +++ b/source/core/defaults.hpp @@ -122,6 +122,12 @@ constexpr int STARTING_LIVES = 3; // Initial lives constexpr float DEATH_DURATION = 3.0f; // Seconds of death animation constexpr float GAME_OVER_DURATION = 5.0f; // Seconds to display game over constexpr float COLLISION_SHIP_ENEMY_AMPLIFIER = 0.80f; // 80% hitbox (generous) + +// Friendly fire system +constexpr bool FRIENDLY_FIRE_ENABLED = true; // Activar friendly fire +constexpr float COLLISION_BULLET_PLAYER_AMPLIFIER = 1.0f; // Hitbox exacto (100%) +constexpr float BULLET_GRACE_PERIOD = 0.2f; // Inmunidad post-disparo (s) + // Transición LEVEL_START (mensajes aleatorios PRE-level) constexpr float LEVEL_START_DURATION = 3.0f; // Duración total constexpr float LEVEL_START_TYPING_RATIO = 0.3f; // 30% escribiendo, 70% mostrando @@ -293,6 +299,7 @@ constexpr bool ENABLED = true; // constexpr const char* CONTINUE = "effects/continue.wav"; // Cuenta atras constexpr const char* EXPLOSION = "effects/explosion.wav"; // Explosión constexpr const char* EXPLOSION2 = "effects/explosion2.wav"; // Explosión alternativa +constexpr const char* FRIENDLY_FIRE_HIT = "effects/friendly_fire.wav"; // Friendly fire hit constexpr const char* INIT_HUD = "effects/init_hud.wav"; // Para la animación del HUD constexpr const char* LASER = "effects/laser_shoot.wav"; // Disparo constexpr const char* LOGO = "effects/logo.wav"; // Logo @@ -513,7 +520,7 @@ namespace FloatingScore { constexpr float LIFETIME = 2.0f; // Duració màxima (segons) constexpr float VELOCITY_Y = -30.0f; // Velocitat vertical (px/s, negatiu = amunt) constexpr float VELOCITY_X = 0.0f; // Velocitat horizontal (px/s) -constexpr float SCALE = 0.75f; // Escala del text (0.75 = 75% del marcador) +constexpr float SCALE = 0.45f; // Escala del text (0.6 = 60% del marcador) constexpr float SPACING = 0.0f; // Espaiat entre caràcters constexpr int MAX_CONCURRENT = 15; // Pool size (= MAX_ORNIS) } // namespace FloatingScore diff --git a/source/game/entities/bala.cpp b/source/game/entities/bala.cpp index e240e2a..c1d288e 100644 --- a/source/game/entities/bala.cpp +++ b/source/game/entities/bala.cpp @@ -19,6 +19,7 @@ Bala::Bala(SDL_Renderer* renderer) angle_(0.0f), velocitat_(0.0f), esta_(false), + grace_timer_(0.0f), brightness_(Defaults::Brightness::BALA) { // [NUEVO] Carregar forma compartida des de fitxer forma_ = Graphics::ShapeLoader::load("bullet.shp"); @@ -34,6 +35,7 @@ void Bala::inicialitzar() { centre_ = {0.0f, 0.0f}; angle_ = 0.0f; velocitat_ = 0.0f; + grace_timer_ = 0.0f; } void Bala::disparar(const Punt& posicio, float angle, uint8_t owner_id) { @@ -57,12 +59,23 @@ void Bala::disparar(const Punt& posicio, float angle, uint8_t owner_id) { // 7 px/frame × 20 FPS = 140 px/s velocitat_ = 140.0f; + // Activar grace period (prevents instant self-collision) + grace_timer_ = Defaults::Game::BULLET_GRACE_PERIOD; + // Reproducir sonido de disparo láser Audio::get()->playSound(Defaults::Sound::LASER, Audio::Group::GAME); } void Bala::actualitzar(float delta_time) { if (esta_) { + // Decrementar grace timer + if (grace_timer_ > 0.0f) { + grace_timer_ -= delta_time; + if (grace_timer_ < 0.0f) { + grace_timer_ = 0.0f; + } + } + mou(delta_time); } } diff --git a/source/game/entities/bala.hpp b/source/game/entities/bala.hpp index 61ae324..183542a 100644 --- a/source/game/entities/bala.hpp +++ b/source/game/entities/bala.hpp @@ -25,6 +25,7 @@ class Bala { bool esta_activa() const { return esta_; } const Punt& get_centre() const { return centre_; } uint8_t get_owner_id() const { return owner_id_; } + float get_grace_timer() const { return grace_timer_; } void desactivar() { esta_ = false; } private: @@ -38,8 +39,9 @@ class Bala { float angle_; float velocitat_; bool esta_; - uint8_t owner_id_; // 0=P1, 1=P2 - float brightness_; // Factor de brillantor (0.0-1.0) + uint8_t owner_id_; // 0=P1, 1=P2 + float grace_timer_; // Grace period timer (0.0 = vulnerable) + float brightness_; // Factor de brillantor (0.0-1.0) void mou(float delta_time); }; diff --git a/source/game/escenes/escena_joc.cpp b/source/game/escenes/escena_joc.cpp index 6a92c02..a9f3a3a 100644 --- a/source/game/escenes/escena_joc.cpp +++ b/source/game/escenes/escena_joc.cpp @@ -476,6 +476,7 @@ void EscenaJoc::actualitzar(float delta_time) { detectar_col·lisions_bales_enemics(); detectar_col·lisio_naus_enemics(); + detectar_col·lisions_bales_jugadors(); debris_manager_.actualitzar(delta_time); gestor_puntuacio_.actualitzar(delta_time); break; @@ -1068,6 +1069,81 @@ void EscenaJoc::detectar_col·lisio_naus_enemics() { } } +void EscenaJoc::detectar_col·lisions_bales_jugadors() { + // Skip if friendly fire disabled + if (!Defaults::Game::FRIENDLY_FIRE_ENABLED) { + return; + } + + // Collision constants (exact hitbox, 1.0x amplification) + constexpr float RADI_NAU = Defaults::Entities::SHIP_RADIUS; + constexpr float RADI_BALA = Defaults::Entities::BULLET_RADIUS; + constexpr float SUMA_RADIS = (RADI_NAU + RADI_BALA) * + Defaults::Game::COLLISION_BULLET_PLAYER_AMPLIFIER; // 15.0 px + constexpr float SUMA_RADIS_QUADRAT = SUMA_RADIS * SUMA_RADIS; // 225.0 + + // Check all active bullets + for (auto& bala : bales_) { + if (!bala.esta_activa()) { + continue; + } + + // Skip bullets in grace period (prevents instant self-collision) + if (bala.get_grace_timer() > 0.0f) { + continue; + } + + const Punt& pos_bala = bala.get_centre(); + uint8_t bullet_owner = bala.get_owner_id(); + + // Check collision with BOTH players + for (uint8_t player_id = 0; player_id < 2; player_id++) { + // Skip if player is dead, invulnerable, or inactive + if (itocado_per_jugador_[player_id] > 0.0f) continue; + if (!naus_[player_id].esta_viva()) continue; + if (naus_[player_id].es_invulnerable()) continue; + + // Skip inactive players + bool jugador_actiu = (player_id == 0) ? config_partida_.jugador1_actiu + : config_partida_.jugador2_actiu; + if (!jugador_actiu) continue; + + const Punt& pos_nau = naus_[player_id].get_centre(); + + // Calculate squared distance (avoid sqrt) + float dx = pos_bala.x - pos_nau.x; + float dy = pos_bala.y - pos_nau.y; + float distancia_quadrada = dx * dx + dy * dy; + + // Check collision + if (distancia_quadrada <= SUMA_RADIS_QUADRAT) { + // *** FRIENDLY FIRE HIT *** + + if (bullet_owner == player_id) { + // CASE 1: Self-hit (own bullet) + // Player loses 1 life, no gain + tocado(player_id); + } else { + // CASE 2: Teammate hit + // Victim loses 1 life + tocado(player_id); + + // Attacker gains 1 life (no cap) + vides_per_jugador_[bullet_owner]++; + } + + // Play distinct sound + Audio::get()->playSound(Defaults::Sound::FRIENDLY_FIRE_HIT, Audio::Group::GAME); + + // Deactivate bullet + bala.desactivar(); + + break; // Bullet only hits once per frame + } + } + } +} + // [NEW] Stage system helper methods void EscenaJoc::dibuixar_missatge_stage(const std::string& missatge) { diff --git a/source/game/escenes/escena_joc.hpp b/source/game/escenes/escena_joc.hpp index c2def11..e20648d 100644 --- a/source/game/escenes/escena_joc.hpp +++ b/source/game/escenes/escena_joc.hpp @@ -83,6 +83,7 @@ class EscenaJoc { void tocado(uint8_t player_id); void detectar_col·lisions_bales_enemics(); // Col·lisions bala-enemic void detectar_col·lisio_naus_enemics(); // Ship-enemy collision detection (plural) + void detectar_col·lisions_bales_jugadors(); // Bullet-player collision detection (friendly fire) void dibuixar_marges() const; // Dibuixar vores de la zona de joc void dibuixar_marcador(); // Dibuixar marcador de puntuació void disparar_bala(uint8_t player_id); // Shoot bullet from player