// bullet.cpp - Implementación de projectils de la ship // © 2026 JailDesigner #include "game/entities/bullet.hpp" #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" #include "game/entities/bullet_config.hpp" #include "game/entities/bullet_registry.hpp" Bullet::Bullet(Rendering::Renderer* renderer) : Entity(renderer), config_(&BulletRegistry::get()) { brightness_ = Defaults::Brightness::BALA; // Cinemàtiques pures: no col·lisionen al PhysicsWorld (body_.radius = 0). // El gameplay (GameScene) gestiona els hits via checkCollisionSwept i la // sortida del PLAYAREA. body_.setMass(config_->physics.mass); body_.radius = 0.0F; body_.restitution = config_->physics.restitution; body_.linear_damping = config_->physics.linear_damping; body_.angular_damping = config_->physics.angular_damping; shape_ = Graphics::ShapeLoader::load(config_->shape.path); if (!shape_ || !shape_->isValid()) { std::cerr << "[Bullet] Error: no s'ha pogut carregar " << config_->shape.path << '\n'; } // Radi de col·lisió derivat del cercle circumscrit de la shape. const float BOUNDING = (shape_ != nullptr) ? shape_->getBoundingRadius() : 0.0F; collision_radius_ = BOUNDING * config_->shape.scale * config_->shape.collision_factor; } void Bullet::init() { is_active_ = false; center_ = {.x = 0.0F, .y = 0.0F}; prev_position_ = {.x = 0.0F, .y = 0.0F}; angle_ = 0.0F; body_.position = Vec2{}; body_.velocity = Vec2{}; body_.angle = 0.0F; body_.angular_velocity = 0.0F; body_.clearAccumulators(); } void Bullet::fire(const Vec2& position, float angle, uint8_t owner_id, float bullet_speed, const BulletConfig* cfg) { is_active_ = true; owner_id_ = owner_id; // Si no es passa cfg, restaurem al config per defecte (BulletRegistry::get): // els slots són reutilitzables i una bala que abans ha estat disparada amb // una variant (p.ex. bullet_long d'enemic) ha de tornar al bullet.shp del // player quan aquest la reutilitza. const BulletConfig* effective = (cfg != nullptr) ? cfg : &BulletRegistry::get(); if (effective != config_) { config_ = effective; shape_ = Graphics::ShapeLoader::load(config_->shape.path); if (!shape_ || !shape_->isValid()) { std::cerr << "[Bullet] Error: no s'ha pogut carregar " << config_->shape.path << '\n'; } const float BOUNDING = (shape_ != nullptr) ? shape_->getBoundingRadius() : 0.0F; collision_radius_ = BOUNDING * config_->shape.scale * config_->shape.collision_factor; body_.setMass(config_->physics.mass); body_.restitution = config_->physics.restitution; body_.linear_damping = config_->physics.linear_damping; body_.angular_damping = config_->physics.angular_damping; } center_ = position; prev_position_ = position; // spawn: swept degenera a punt-cercle angle_ = angle; // Sincronizar el body físic: posició + velocitat cartesiana. // angle - PI/2 perquè angle=0 apunta cap amunt (eje Y negatiu 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(); Audio::get()->playSound(Defaults::Sound::LASER, Audio::Group::GAME); } void Bullet::update(float /*delta_time*/) { // No-op: la desactivació per fora-de-zone viu a // Systems::Collision::desactivateOutOfBoundsBullets() perquè així té accés // al DebrisManager i pot generar el "trencament" visual de la bala alhora. // El moviment l'integra PhysicsWorld; postUpdate sincronitza center_ i prev_position_. } void Bullet::postUpdate(float /*delta_time*/) { prev_position_ = center_; center_ = body_.position; } void Bullet::desactivar() { is_active_ = false; body_.velocity = Vec2{}; body_.angular_velocity = 0.0F; } void Bullet::draw() const { if (is_active_ && shape_) { Rendering::renderShape(renderer_, shape_, center_, angle_, 1.0F, 1.0F, brightness_, config_->colors.normal); } }