From d169a1997c7fbe2c1df9cbd046ccd9f9041dd47f Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Thu, 21 May 2026 10:20:42 +0200 Subject: [PATCH] feat(enemy): afegeix estat "wounded" amb timer i API base (Fase 1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Defaults::Palette::WOUNDED ({255,215,0}) dorat per a parpadeig - Defaults::Enemies::Wounded::{DURATION, BLINK_HZ} - Enemy: wounded_timer_, wound_expired_this_frame_ - API: herir(), isWounded(), getWoundedTimer(), woundExpiredThisFrame(), consumeWoundExpired(), applyImpulse() - update() decrementa timer i marca expiració al creuar 0 - destruir() reseteja l'estat wounded Sense efectes visuals ni canvis de comportament: cap callsite invoca encara herir() ni applyImpulse(). Build verda i smoke test xvfb OK. Co-Authored-By: Claude Opus 4.7 (1M context) --- source/core/defaults/enemies.hpp | 6 ++ source/core/defaults/palette.hpp | 1 + source/game/entities/enemy.cpp | 21 ++++ source/game/entities/enemy.hpp | 176 +++++++++++++++++-------------- 4 files changed, 123 insertions(+), 81 deletions(-) diff --git a/source/core/defaults/enemies.hpp b/source/core/defaults/enemies.hpp index d321966..1d6a960 100644 --- a/source/core/defaults/enemies.hpp +++ b/source/core/defaults/enemies.hpp @@ -70,6 +70,12 @@ namespace Defaults::Enemies { constexpr float ROTACIO_ACCEL_MULTIPLIER_MAX = 4.0F; // Max speed multiplier [more dramatic] } // namespace Animation + // Wounded state (entre primer impacto y explosión) + namespace Wounded { + constexpr float DURATION = 1.0F; // Segundos en estado herido antes de explotar + constexpr float BLINK_HZ = 10.0F; // Frecuencia de parpadeo color tipo ↔ dorado + } // namespace Wounded + // Spawn safety and invulnerability system namespace Spawn { // Safe spawn distance from player diff --git a/source/core/defaults/palette.hpp b/source/core/defaults/palette.hpp index 2c3eae5..46753de 100644 --- a/source/core/defaults/palette.hpp +++ b/source/core/defaults/palette.hpp @@ -15,5 +15,6 @@ namespace Defaults::Palette { constexpr SDL_Color PENTAGON = {.r = 120, .g = 170, .b = 255, .a = 255}; // Azul "esquivador" constexpr SDL_Color QUADRAT = {.r = 255, .g = 110, .b = 110, .a = 255}; // Rojo "tank" constexpr SDL_Color MOLINILLO = {.r = 255, .g = 130, .b = 255, .a = 255}; // Magenta agresivo + constexpr SDL_Color WOUNDED = {.r = 255, .g = 215, .b = 0, .a = 255}; // Dorado: enemigo herido } // namespace Defaults::Palette diff --git a/source/game/entities/enemy.cpp b/source/game/entities/enemy.cpp index 033b942..a8d9f28 100644 --- a/source/game/entities/enemy.cpp +++ b/source/game/entities/enemy.cpp @@ -177,6 +177,17 @@ void Enemy::update(float delta_time) { return; } + // Decremento de timer "herido"; al cruzar 0 marca expiración para que el + // system layer dispare la explosión diferida. + wound_expired_this_frame_ = false; + if (wounded_timer_ > 0.0F) { + wounded_timer_ -= delta_time; + if (wounded_timer_ <= 0.0F) { + wounded_timer_ = 0.0F; + wound_expired_this_frame_ = true; + } + } + // Decremento de invulnerabilidad + LERP de brightness if (timer_invulnerabilitat_ > 0.0F) { timer_invulnerabilitat_ -= delta_time; @@ -242,6 +253,16 @@ void Enemy::destruir() { body_.velocity = Vec2{}; body_.angular_velocity = 0.0F; body_.radius = 0.0F; // No colisiona mientras está inactivo + wounded_timer_ = 0.0F; + wound_expired_this_frame_ = false; +} + +void Enemy::herir() { + wounded_timer_ = Defaults::Enemies::Wounded::DURATION; +} + +void Enemy::applyImpulse(const Vec2& impulse) { + body_.applyImpulse(impulse); } void Enemy::setVelocity(float speed) { diff --git a/source/game/entities/enemy.hpp b/source/game/entities/enemy.hpp index f0ab5ff..4d24d00 100644 --- a/source/game/entities/enemy.hpp +++ b/source/game/entities/enemy.hpp @@ -19,104 +19,118 @@ enum class EnemyType : uint8_t { // Estado de animación (palpitación + rotación acelerada) struct EnemyAnimation { - // Palpitación (efecto respiración) - bool palpitacio_activa = false; - float palpitacio_fase = 0.0F; - float palpitacio_frequencia = 2.0F; - float palpitacio_amplitud = 0.15F; - float palpitacio_temps_restant = 0.0F; + // Palpitación (efecto respiración) + bool palpitacio_activa = false; + float palpitacio_fase = 0.0F; + float palpitacio_frequencia = 2.0F; + float palpitacio_amplitud = 0.15F; + float palpitacio_temps_restant = 0.0F; - // Aceleración de rotación visual (modulación a largo plazo) - float drotacio_base = 0.0F; - float drotacio_objetivo = 0.0F; - float drotacio_t = 0.0F; - float drotacio_duracio = 0.0F; + // Aceleración de rotación visual (modulación a largo plazo) + float drotacio_base = 0.0F; + float drotacio_objetivo = 0.0F; + float drotacio_t = 0.0F; + float drotacio_duracio = 0.0F; }; class Enemy : public Entities::Entity { - public: - Enemy() - : Entity(nullptr) {} - explicit Enemy(Rendering::Renderer* renderer); + public: + Enemy() + : Entity(nullptr) {} + explicit Enemy(Rendering::Renderer* renderer); - void init() override { init(EnemyType::PENTAGON, nullptr); } - void init(EnemyType type, const Vec2* ship_pos = nullptr); - void update(float delta_time) override; - void postUpdate(float delta_time) override; - void draw() const override; + void init() override { init(EnemyType::PENTAGON, nullptr); } + void init(EnemyType type, const Vec2* ship_pos = nullptr); + void update(float delta_time) override; + void postUpdate(float delta_time) override; + void draw() const override; - // Override: Interfaz de Entity - [[nodiscard]] auto isActive() const -> bool override { return esta_; } + // Override: Interfaz de Entity + [[nodiscard]] auto isActive() const -> bool override { return esta_; } - // Override: Interfaz de colisión - [[nodiscard]] auto getCollisionRadius() const -> float override { - return Defaults::Entities::ENEMY_RADIUS; - } - [[nodiscard]] auto isCollidable() const -> bool override { - return esta_ && timer_invulnerabilitat_ <= 0.0F; - } + // Override: Interfaz de colisión + [[nodiscard]] auto getCollisionRadius() const -> float override { + return Defaults::Entities::ENEMY_RADIUS; + } + [[nodiscard]] auto isCollidable() const -> bool override { + return esta_ && timer_invulnerabilitat_ <= 0.0F; + } - // Marcar destruido (desactiva el cuerpo físicamente: radius=0) - void destruir(); + // Marcar destruido (desactiva el cuerpo físicamente: radius=0) + void destruir(); - // Getters - [[nodiscard]] auto getRotationDelta() const -> float { return drotacio_; } - [[nodiscard]] auto getVelocityVector() const -> Vec2 { return body_.velocity; } + // Getters + [[nodiscard]] auto getRotationDelta() const -> float { return drotacio_; } + [[nodiscard]] auto getVelocityVector() const -> Vec2 { return body_.velocity; } - // Set ship position reference for tracking behavior - void setShipPosition(const Vec2* ship_pos) { ship_position_ = ship_pos; } + // Set ship position reference for tracking behavior + void setShipPosition(const Vec2* ship_pos) { ship_position_ = ship_pos; } - // Stage system API (base stats) - [[nodiscard]] auto getBaseVelocity() const -> float; - [[nodiscard]] auto getBaseRotation() const -> float; - [[nodiscard]] auto getType() const -> EnemyType { return type_; } + // Stage system API (base stats) + [[nodiscard]] auto getBaseVelocity() const -> float; + [[nodiscard]] auto getBaseRotation() const -> float; + [[nodiscard]] auto getType() const -> EnemyType { return type_; } - // Setters para multiplicadores de dificultad (stage system). - // Establecen la velocidad escalar deseada manteniendo la dirección - // actual del body_.velocity. - void setVelocity(float speed); - void setRotation(float rot) { - drotacio_ = rot; - animacio_.drotacio_base = rot; - } - void setTrackingStrength(float strength); + // Setters para multiplicadores de dificultad (stage system). + // Establecen la velocidad escalar deseada manteniendo la dirección + // actual del body_.velocity. + void setVelocity(float speed); + void setRotation(float rot) { + drotacio_ = rot; + animacio_.drotacio_base = rot; + } + void setTrackingStrength(float strength); - // Invulnerabilidad - [[nodiscard]] auto isInvulnerable() const -> bool { return timer_invulnerabilitat_ > 0.0F; } - [[nodiscard]] auto getInvulnerabilityTime() const -> float { return timer_invulnerabilitat_; } + // Invulnerabilidad + [[nodiscard]] auto isInvulnerable() const -> bool { return timer_invulnerabilitat_ > 0.0F; } + [[nodiscard]] auto getInvulnerabilityTime() const -> float { return timer_invulnerabilitat_; } - private: - // Miembros específicos (heredados: renderer_, shape_, center_, angle_, brightness_, body_). - // Inicializados en la declaración: el ctor por defecto deja al enemy en estado "inactivo - // como pentágono", coherente con lo que harán init() o el ctor con renderer al activarlo. - float drotacio_{0.0F}; // Velocidad angular visual (rad/s) — solo decoración, separada de body_.angular_velocity - float rotacio_{0.0F}; // Rotación visual acumulada (no afecta movimiento) - bool esta_{false}; + // Estado "herido": entre primer impacto de bala y explosión diferida. + void herir(); + [[nodiscard]] auto isWounded() const -> bool { return wounded_timer_ > 0.0F; } + [[nodiscard]] auto getWoundedTimer() const -> float { return wounded_timer_; } + [[nodiscard]] auto woundExpiredThisFrame() const -> bool { return wound_expired_this_frame_; } + void consumeWoundExpired() { wound_expired_this_frame_ = false; } - EnemyType type_{EnemyType::PENTAGON}; - EnemyAnimation animacio_; + // Aplica un impulso (cambio inmediato de velocidad mass-aware) al cuerpo físico. + void applyImpulse(const Vec2& impulse); - // Comportamiento type-specific - float tracking_timer_{0.0F}; // Quadrat: tiempo desde último update de dirección - const Vec2* ship_position_{nullptr}; // Puntero a posición de la nave (para tracking) - float tracking_strength_{0.0F}; // Quadrat: intensidad de tracking (0.0-1.5), default 0.5 - float direction_change_timer_{0.0F}; // Pentagon: tiempo para próximo cambio de dirección + private: + // Miembros específicos (heredados: renderer_, shape_, center_, angle_, brightness_, body_). + // Inicializados en la declaración: el ctor por defecto deja al enemy en estado "inactivo + // como pentágono", coherente con lo que harán init() o el ctor con renderer al activarlo. + float drotacio_{0.0F}; // Velocidad angular visual (rad/s) — solo decoración, separada de body_.angular_velocity + float rotacio_{0.0F}; // Rotación visual acumulada (no afecta movimiento) + bool esta_{false}; - // Invulnerabilidad post-spawn - float timer_invulnerabilitat_{0.0F}; + EnemyType type_{EnemyType::PENTAGON}; + EnemyAnimation animacio_; - // Métodos privados - void updateAnimation(float delta_time); - void updatePalpitation(float delta_time); - void updateRotationAcceleration(float delta_time); - void behaviorPentagon(float delta_time); - void behaviorQuadrat(float delta_time); - void behaviorMolinillo(float delta_time); - [[nodiscard]] auto computeCurrentScale() const -> float; - // Estático: solo opera sobre ship_pos pasado; no consulta estado del enemy. - static auto attemptSafeSpawn(const Vec2& ship_pos, float& out_x, float& out_y) -> bool; + // Comportamiento type-specific + float tracking_timer_{0.0F}; // Quadrat: tiempo desde último update de dirección + const Vec2* ship_position_{nullptr}; // Puntero a posición de la nave (para tracking) + float tracking_strength_{0.0F}; // Quadrat: intensidad de tracking (0.0-1.5), default 0.5 + float direction_change_timer_{0.0F}; // Pentagon: tiempo para próximo cambio de dirección - // Helper: setear body_.velocity desde un ángulo y magnitud. - // angle_movement=0 apunta hacia arriba (eje Y negativo SDL). - void setVelocityFromAngle(float angle_movement, float speed); + // Invulnerabilidad post-spawn + float timer_invulnerabilitat_{0.0F}; + + // Estado "herido": timer cuenta atrás; al cruzar 0 se marca expiración. + float wounded_timer_{0.0F}; + bool wound_expired_this_frame_{false}; + + // Métodos privados + void updateAnimation(float delta_time); + void updatePalpitation(float delta_time); + void updateRotationAcceleration(float delta_time); + void behaviorPentagon(float delta_time); + void behaviorQuadrat(float delta_time); + void behaviorMolinillo(float delta_time); + [[nodiscard]] auto computeCurrentScale() const -> float; + // Estático: solo opera sobre ship_pos pasado; no consulta estado del enemy. + static auto attemptSafeSpawn(const Vec2& ship_pos, float& out_x, float& out_y) -> bool; + + // Helper: setear body_.velocity desde un ángulo y magnitud. + // angle_movement=0 apunta hacia arriba (eje Y negativo SDL). + void setVelocityFromAngle(float angle_movement, float speed); };