// bullet.cpp - Implementación de projectils de la ship // © 1999 Visente i Sergi (versión Pascal) // © 2025 Port a C++20 con SDL3 #include "game/entities/bullet.hpp" #include #include #include #include #include "core/audio/audio.hpp" #include "core/defaults.hpp" #include "core/entities/entity.hpp" #include "core/graphics/shape_loader.hpp" #include "core/rendering/shape_renderer.hpp" #include "core/types.hpp" #include "game/constants.hpp" namespace { // Velocidad escalar de las balas (px/s). Conserva el feel del Pascal original // (7 px/frame × 20 FPS = 140 px/s). constexpr float BULLET_SPEED = 140.0F; } // namespace Bullet::Bullet(Rendering::Renderer* renderer) : Entity(renderer), esta_(false), owner_id_(0), grace_timer_(0.0F) { // Brightness específico para balas brightness_ = Defaults::Brightness::BALA; // Configuración del cuerpo físico. // Las balas son cinemáticas: no colisionan con otros bodies ni paredes. // El gameplay (GameScene) gestiona los hits con check_collision y la // salida del PLAYAREA. Por eso radius=0 en el world (no participa en // resolveBodyCollisions ni resolveBoundsCollisions). body_.setMass(0.5F); // Ligera (no afecta a nadie, pero por consistencia) body_.radius = 0.0F; // Sin colisión física (cinemática pura) body_.restitution = 0.0F; // Irrelevante (no rebota) body_.linear_damping = 0.0F; // Sin fricción (movimiento rectilíneo uniforme) body_.angular_damping = 0.0F; // Cargar shape compartida desde archivo shape_ = Graphics::ShapeLoader::load("bullet.shp"); if (!shape_ || !shape_->isValid()) { std::cerr << "[Bullet] Error: no s'ha pogut load bullet.shp" << '\n'; } } void Bullet::init() { // Inicialment inactiva esta_ = false; center_ = {.x = 0.0F, .y = 0.0F}; angle_ = 0.0F; grace_timer_ = 0.0F; // Reset del cuerpo físico body_.position = Vec2{}; body_.velocity = Vec2{}; body_.angle = 0.0F; body_.angular_velocity = 0.0F; body_.clearAccumulators(); } void Bullet::disparar(const Vec2& position, float angle, uint8_t owner_id) { // Activar bullet esta_ = true; // Almacenar propietario (0=P1, 1=P2) owner_id_ = owner_id; // Activar grace period (prevents instant self-collision) grace_timer_ = Defaults::Game::BULLET_GRACE_PERIOD; // Posición y orientación iniciales = ship center_ = position; angle_ = angle; // Sincronizar el body físico: posición + velocidad cartesiana // angle - PI/2 porque angle=0 apunta hacia arriba (eje Y negativo SDL) body_.position = position; body_.angle = angle; const float DIR_X = std::cos(angle - (Constants::PI / 2.0F)); const float DIR_Y = std::sin(angle - (Constants::PI / 2.0F)); body_.velocity = Vec2{.x = DIR_X * BULLET_SPEED, .y = DIR_Y * BULLET_SPEED}; body_.angular_velocity = 0.0F; body_.clearAccumulators(); // Reproducir sonido de disparo láser Audio::get()->playSound(Defaults::Sound::LASER, Audio::Group::GAME); } void Bullet::update(float delta_time) { if (!esta_) { return; } // Decrementar grace timer if (grace_timer_ > 0.0F) { grace_timer_ -= delta_time; grace_timer_ = std::max(grace_timer_, 0.0F); } // El movimiento real lo hace PhysicsWorld::update() (integración). // Aquí solo lógica de estado: detectar salida del PLAYAREA y desactivar. float min_x; float max_x; float min_y; float max_y; Constants::obtenir_limits_zona_segurs(Defaults::Entities::BULLET_RADIUS, min_x, max_x, min_y, max_y); if (body_.position.x < min_x || body_.position.x > max_x || body_.position.y < min_y || body_.position.y > max_y) { desactivar(); } } void Bullet::postUpdate(float /*delta_time*/) { // Sincronizar mirror desde body_ tras la integración del world. center_ = body_.position; // angle_ no cambia (las balas no rotan visualmente). } void Bullet::desactivar() { esta_ = false; // Detener el cuerpo físico para que no acumule deriva mientras inactiva. body_.velocity = Vec2{}; body_.angular_velocity = 0.0F; } void Bullet::draw() const { if (esta_ && shape_) { // Les bales roten segons l'angle de trayectòria (estático tras disparo) Rendering::render_shape(renderer_, shape_, center_, angle_, 1.0F, 1.0F, brightness_); } }