// enemy.hpp - Clase para enemigos (ORNIs) // © 2026 JailDesigner #pragma once #include #include #include "core/entities/entity.hpp" #include "core/types.hpp" // Tipo de enemy enum class EnemyType : uint8_t { PENTAGON = 0, // Pentágono esquivador (zigzag) SQUARE = 1, // Square perseguidor (tracks ship) PINWHEEL = 2, // Molinillo agresivo (rápido, girando) STAR = 3 // Estrella de 5 puntes (clone visual de Pentagon, comportament zigzag) }; // Forward declaration — EnemyConfig viu a enemy_config.hpp i s'inclou només a enemy.cpp. struct EnemyConfig; // Estado de animación (palpitación + rotación acelerada) struct EnemyAnimation { // Palpitación (efecto respiración) bool pulse_active = false; float pulse_phase = 0.0F; float pulse_frequency = 2.0F; float pulse_amplitude = 0.15F; float pulse_time_remaining = 0.0F; // Aceleración de rotación visual (modulación a largo plazo) float rotation_delta_base = 0.0F; float rotation_delta_target = 0.0F; float rotation_delta_t = 0.0F; float rotation_delta_duration = 0.0F; }; class Enemy : public Entities::Entity { 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; // Override: Interfaz de Entity [[nodiscard]] auto isActive() const -> bool override { return is_active_; } // Override: Interfaz de colisión. El radi ve del config carregat per tipus. [[nodiscard]] auto getCollisionRadius() const -> float override { return collision_radius_; } // Mentre fa spawn (invulnerable) segueix col·lisionant: les bales el // poden abatre i el cos físic rebota amb la nau. El damage a la nau // segueix filtrat per `isInvulnerable()` al detectShipEnemy. [[nodiscard]] auto isCollidable() const -> bool override { return is_active_; } // Marcar destruido (desactiva el cuerpo físicamente: radius=0) void destroy(); // Getters [[nodiscard]] auto getRotationDelta() const -> float { return rotation_delta_; } [[nodiscard]] auto getVelocityVector() const -> Vec2 { return body_.velocity; } // Configuració activa (carregada al darrer init()). Vàlida mentre l'enemic // ha estat inicialitzat almenys un cop; abans és nullptr. [[nodiscard]] auto getConfig() const -> const EnemyConfig& { return *config_; } // 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_; } // 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) { rotation_delta_ = rot; animation_.rotation_delta_base = rot; } void setTrackingStrength(float strength); // Invulnerabilidad [[nodiscard]] auto isInvulnerable() const -> bool { return invulnerability_timer_ > 0.0F; } [[nodiscard]] auto getInvulnerabilityTime() const -> float { return invulnerability_timer_; } // Estado "herido": entre primer impacto de bala y explosión diferida. // shooter_id: id del jugador que herí; 0xFF = sin atribución (cadena, etc.). void hurt(uint8_t shooter_id = 0xFF); [[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; } [[nodiscard]] auto getLastHitBy() const -> uint8_t { return last_hit_by_; } // Aplica un impulso (cambio inmediato de velocidad mass-aware) al cuerpo físico. void applyImpulse(const Vec2& impulse); private: // Configuració carregada per tipus (apunta a una entrada de EnemyRegistry). // nullptr abans del primer init(); per això getConfig() només és vàlid post-init. const EnemyConfig* config_{nullptr}; // Cache local del radi (per evitar dereferenciar config_ a getCollisionRadius); // s'actualitza a init() segons el tipus. float collision_radius_{0.0F}; float rotation_delta_{0.0F}; // Velocidad angular visual (rad/s) float rotation_{0.0F}; // Rotación visual acumulada bool is_active_{false}; EnemyType type_{EnemyType::PENTAGON}; EnemyAnimation animation_; // Comportamiento type-specific float tracking_timer_{0.0F}; const Vec2* ship_position_{nullptr}; float tracking_strength_{0.0F}; float direction_change_timer_{0.0F}; // Invulnerabilidad post-spawn float invulnerability_timer_{0.0F}; // Estado "herido" float wounded_timer_{0.0F}; bool wound_expired_this_frame_{false}; uint8_t last_hit_by_{0xFF}; // Métodos privados void updateAnimation(float delta_time); void updatePulse(float delta_time); void updateRotationAcceleration(float delta_time); void behaviorPentagon(float delta_time); void behaviorSquare(float delta_time); void behaviorPinwheel(float delta_time); [[nodiscard]] auto computeCurrentScale() const -> float; // Static: passa els paràmetres com a args per no acoblar a *this. static auto attemptSafeSpawn(const Vec2& ship_pos, float collision_radius, float safety_distance, 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); };