diff --git a/data/entities/bullet/bullet.yaml b/data/entities/bullet/bullet.yaml new file mode 100644 index 0000000..d90fb19 --- /dev/null +++ b/data/entities/bullet/bullet.yaml @@ -0,0 +1,22 @@ +name: bullet + +# Shape de la bala. El bounding_radius del .shp dóna el hitbox base (~3 px); +# scale el modula visualment i pel hitbox. +shape: + path: bullet.shp + scale: 1.0 + collision_factor: 1.0 + +# Cinemàtica pura: la bala no col·lisiona físicament al PhysicsWorld +# (body_.radius = 0 al spawn), però sí participa al gameplay via +# checkCollisionSwept. La mass i l'impact_momentum_factor es fan servir +# només per calcular l'impuls que rep l'enemic en impactar. +physics: + mass: 0.5 + restitution: 0.0 # irrelevant (no rebota) + linear_damping: 0.0 # movement rectilini uniforme + angular_damping: 0.0 + impact_momentum_factor: 3.0 # factor de transferència de moment bala→enemic + +colors: + normal: [155, 255, 175] # verd laser diff --git a/source/core/defaults/entities.hpp b/source/core/defaults/entities.hpp index 3de3424..44f0c5a 100644 --- a/source/core/defaults/entities.hpp +++ b/source/core/defaults/entities.hpp @@ -8,8 +8,9 @@ namespace Defaults::Entities { constexpr int MAX_ORNIS = 15; constexpr int MAX_BULLETS = 50; - // SHIP_RADIUS migrat a data/entities/player/player.yaml (physics.collision_radius). - // ENEMY_RADIUS migrat a data/entities//.yaml (physics.collision_radius). - constexpr float BULLET_RADIUS = 3.0F; + // SHIP_RADIUS / ENEMY_RADIUS / BULLET_RADIUS han migrat: ara cada entitat + // calcula el seu collision_radius com a + // shape.bounding_radius × shape.scale × shape.collision_factor + // a partir del seu YAML (data/entities//.yaml). } // namespace Defaults::Entities diff --git a/source/core/defaults/palette.hpp b/source/core/defaults/palette.hpp index 42158a3..4b2217a 100644 --- a/source/core/defaults/palette.hpp +++ b/source/core/defaults/palette.hpp @@ -14,10 +14,10 @@ namespace Defaults::Palette { // brillantor perceptual sota el bloom (sense alterar la identitat de color). // El canal dominant es manté a 255 a cada color per maximitzar la saturació // visible quan el halo s'expandeix. - constexpr SDL_Color BULLET = {.r = 155, .g = 255, .b = 175, .a = 255}; // Verde laser - // SHIP s'ha migrat a data/entities/player/player.yaml (colors.normal). - // PENTAGON, SQUARE, PINWHEEL i WOUNDED han migrat a cada enemy YAML - // (colors.normal i colors.wounded). - // BULLET es queda compartit fins a la migració del bullet a YAML. + // Tots els colors d'entitats han migrat al seu YAML respectiu + // (data/entities//.yaml, secció `colors`): + // - SHIP → player.yaml + // - PENTAGON / SQUARE / PINWHEEL / WOUNDED → cada enemy.yaml + // - BULLET → bullet.yaml } // namespace Defaults::Palette diff --git a/source/core/defaults/physics.hpp b/source/core/defaults/physics.hpp index 2f3f741..d448694 100644 --- a/source/core/defaults/physics.hpp +++ b/source/core/defaults/physics.hpp @@ -3,64 +3,55 @@ #pragma once -namespace Defaults::Physics { +// NOTA: els paràmetres del player (rotation_speed, acceleration, +// max_velocity, death_impact_factor) viuen a data/entities/player/player.yaml. +// Els paràmetres específics de la bala (mass, restitution, damping, +// impact_momentum_factor) viuen a data/entities/bullet/bullet.yaml. +// Aquest fitxer només conté els paràmetres compartits del subsistema de +// debris (explosions visuals). - // NOTA: els paràmetres específics de la nau del player (rotation_speed, - // acceleration, max_velocity, death_impact_factor) viuen ara a - // data/entities/player/player.yaml. La migració d'aquests fitxers va - // començar amb la nau; els enemics i les bales són els següents. +namespace Defaults::Physics::Debris { - // Bullet — impacto físico contra enemigo (impulse mass-aware). - // Model: el impulse és el moment lineal de la bala (m·v) multiplicat per - // un factor de transferència [0..1]. 1.0 = transfereix tot el moment - // (col·lisió perfectament inelàstica), 0.5 = transfereix la meitat. - namespace Bullet { - constexpr float IMPACT_MOMENTUM_FACTOR = 3.0F; // Factor de transferència de moment bala→enemic - } // namespace Bullet + constexpr float SPEED_BASE = 80.0F; // Velocidad inicial (px/s) + constexpr float VARIACIO_SPEED = 40.0F; // ±variació aleatòria (px/s) + constexpr float ACCELERACIO = -60.0F; // Fricció/desacceleració (px/s²) + constexpr float ROTATION_MIN = 0.1F; // Rotación mínima (rad/s ~5.7°/s) + constexpr float ROTATION_MAX = 0.3F; // Rotación màxima (rad/s ~17.2°/s) + constexpr float TEMPS_VIDA = 2.0F; // Vida mínima garantida (s) — després pot morir per velocitat baixa + constexpr float TEMPS_VIDA_NAU = 3.0F; // Ship debris min lifetime (matches DEATH_DURATION) + constexpr float SHRINK_RATE = 1.0F; // Reducció de mida (1.0 = encoge a 0 al final del min_lifetime) - // Explosions (debris physics) - namespace Debris { - constexpr float SPEED_BASE = 80.0F; // Velocidad inicial (px/s) - constexpr float VARIACIO_SPEED = 40.0F; // ±variació aleatòria (px/s) - constexpr float ACCELERACIO = -60.0F; // Fricció/desacceleració (px/s²) - constexpr float ROTATION_MIN = 0.1F; // Rotación mínima (rad/s ~5.7°/s) - constexpr float ROTATION_MAX = 0.3F; // Rotación màxima (rad/s ~17.2°/s) - constexpr float TEMPS_VIDA = 2.0F; // Vida mínima garantida (s) — després pot morir per velocitat baixa - constexpr float TEMPS_VIDA_NAU = 3.0F; // Ship debris min lifetime (matches DEATH_DURATION) - constexpr float SHRINK_RATE = 1.0F; // Reducció de mida (1.0 = encoge a 0 al final del min_lifetime) + // Política de mort: passat el min_lifetime, el fragment mor quan la + // seva velocity cau per sota d'aquest llindar. Així els fragments + // ràpids no "popen" en moviment. + constexpr float MIN_SPEED_TO_DIE = 5.0F; // px/s — al cuadrat per evitar sqrt en update + constexpr float MIN_SPEED_TO_DIE_SQ = MIN_SPEED_TO_DIE * MIN_SPEED_TO_DIE; - // Política de mort: passat el min_lifetime, el fragment mor quan la - // seva velocity cau per sota d'aquest llindar. Així els fragments - // ràpids no "popen" en moviment. - constexpr float MIN_SPEED_TO_DIE = 5.0F; // px/s — al cuadrat per evitar sqrt en update - constexpr float MIN_SPEED_TO_DIE_SQ = MIN_SPEED_TO_DIE * MIN_SPEED_TO_DIE; + // Rebot contra els límits del PLAYAREA (mateix patró que enemics/ship). + // 0.7 = 70% de l'energia conservada al rebot. + constexpr float RESTITUTION_BOUNDS = 0.7F; - // Rebot contra els límits del PLAYAREA (mateix patró que enemics/ship). - // 0.7 = 70% de l'energia conservada al rebot. - constexpr float RESTITUTION_BOUNDS = 0.7F; + // Herència de velocity angular (trayectorias curvas) + constexpr float INHERITANCE_FACTOR_MIN = 0.7F; // Mínimo 70% del drotacio heredat + constexpr float INHERITANCE_FACTOR_MAX = 1.0F; // Màxim 100% del drotacio heredat + constexpr float FRICCIO_ANGULAR = 0.5F; // Desacceleració angular (rad/s²) - // Herència de velocity angular (trayectorias curvas) - constexpr float INHERITANCE_FACTOR_MIN = 0.7F; // Mínimo 70% del drotacio heredat - constexpr float INHERITANCE_FACTOR_MAX = 1.0F; // Màxim 100% del drotacio heredat - constexpr float FRICCIO_ANGULAR = 0.5F; // Desacceleració angular (rad/s²) + // Velocity heredada de la nau a l'explosió (80% del feel original). + constexpr float SHIP_VELOCITY_INHERITANCE = 0.8F; - // Velocity heredada de la nau a l'explosió (80% del feel original). - constexpr float SHIP_VELOCITY_INHERITANCE = 0.8F; + // Velocity heredada de l'enemic a l'explosió (palanca per a tuneo). + // 1.0 = inèrcia completa; >1.0 amplifica la deriva; <1.0 la atenua. + constexpr float ENEMY_VELOCITY_INHERITANCE = 1.0F; - // Velocity heredada de l'enemic a l'explosió (palanca per a tuneo). - // 1.0 = inèrcia completa; >1.0 amplifica la deriva; <1.0 la atenua. - constexpr float ENEMY_VELOCITY_INHERITANCE = 1.0F; + // Tuneig específic de l'explosió d'enemic (overrides als defaults + // que es passen com a paràmetres opcionals a explode()). + constexpr float ENEMY_LIFETIME = 2.5F; // Vida mínima del debris (s) — els que segueixen movent-se viuen més + constexpr float ENEMY_FRICTION = -30.0F; // Fricció més suau perquè s'estenguin més + constexpr int ENEMY_SEGMENT_MULTIPLIER = 1; // Sense còpies (5 cares = 5 trossos); >1 produeix grups sincronitzats - // Tuneig específic de l'explosió d'enemic (overrides als defaults - // que es passen com a paràmetres opcionals a explode()). - constexpr float ENEMY_LIFETIME = 2.5F; // Vida mínima del debris (s) — els que segueixen movent-se viuen més - constexpr float ENEMY_FRICTION = -30.0F; // Fricció més suau perquè s'estenguin més - constexpr int ENEMY_SEGMENT_MULTIPLIER = 1; // Sense còpies (5 cares = 5 trossos); >1 produeix grups sincronitzats + // Angular velocity sin for trajectory inheritance + // Excess above this threshold is converted to tangential linear velocity + // Prevents "vortex trap" problem with high-rotation enemies + constexpr float SPEED_ROT_MAX = 1.5F; // rad/s (~86°/s) - // Angular velocity sin for trajectory inheritance - // Excess above this threshold is converted to tangential linear velocity - // Prevents "vortex trap" problem with high-rotation enemies - constexpr float SPEED_ROT_MAX = 1.5F; // rad/s (~86°/s) - } // namespace Debris - -} // namespace Defaults::Physics +} // namespace Defaults::Physics::Debris diff --git a/source/game/entities/bullet.cpp b/source/game/entities/bullet.cpp index f5cdd41..f85fdaa 100644 --- a/source/game/entities/bullet.cpp +++ b/source/game/entities/bullet.cpp @@ -14,38 +14,39 @@ #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) { - // Brightness específico para balas + : Entity(renderer), + config_(&BulletRegistry::get()) { 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; + // 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; - // Cargar shape compartida desde archivo - shape_ = Graphics::ShapeLoader::load("bullet.shp"); + shape_ = Graphics::ShapeLoader::load(config_->shape.path); if (!shape_ || !shape_->isValid()) { - std::cerr << "[Bullet] Error: no s'ha pogut load bullet.shp" << '\n'; + 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() { - // Inicialment inactiva is_active_ = false; center_ = {.x = 0.0F, .y = 0.0F}; prev_position_ = {.x = 0.0F, .y = 0.0F}; angle_ = 0.0F; - // Reset del cuerpo físico body_.position = Vec2{}; body_.velocity = Vec2{}; body_.angle = 0.0F; @@ -54,19 +55,15 @@ void Bullet::init() { } void Bullet::fire(const Vec2& position, float angle, uint8_t owner_id, float bullet_speed) { - // Activar bullet is_active_ = true; - - // Almacenar propietario (0=P1, 1=P2) owner_id_ = owner_id; - // Posición y orientación iniciales = ship center_ = position; - prev_position_ = position; // Al spawn no hi ha moviment encara: swept degenera a punt-cercle + prev_position_ = position; // spawn: swept degenera a punt-cercle angle_ = angle; - // Sincronizar el body físico: posición + velocidad cartesiana - // angle - PI/2 porque angle=0 apunta hacia arriba (eje Y negativo SDL) + // 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)); @@ -75,7 +72,6 @@ void Bullet::fire(const Vec2& position, float angle, uint8_t owner_id, float bul body_.angular_velocity = 0.0F; body_.clearAccumulators(); - // Reproducir sonido de disparo láser Audio::get()->playSound(Defaults::Sound::LASER, Audio::Group::GAME); } @@ -87,24 +83,18 @@ void Bullet::update(float /*delta_time*/) { } void Bullet::postUpdate(float /*delta_time*/) { - // Captura la posició al final del frame anterior abans de sobreescriure center_; - // així el sistema de col·lisions pot fer swept (segment-vs-cercle) entre prev_position_ - // i la nova center_, evitant tunneling a velocitats altes. prev_position_ = center_; center_ = body_.position; - // angle_ no cambia (las balas no rotan visualmente). } void Bullet::desactivar() { is_active_ = 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 (is_active_ && shape_) { - // Les bales roten segons l'angle de trayectòria (estático tras disparo) - Rendering::renderShape(renderer_, shape_, center_, angle_, 1.0F, 1.0F, brightness_, Defaults::Palette::BULLET); + Rendering::renderShape(renderer_, shape_, center_, angle_, 1.0F, 1.0F, brightness_, config_->colors.normal); } } diff --git a/source/game/entities/bullet.hpp b/source/game/entities/bullet.hpp index b520aab..c63c08c 100644 --- a/source/game/entities/bullet.hpp +++ b/source/game/entities/bullet.hpp @@ -6,10 +6,12 @@ #include -#include "core/defaults.hpp" #include "core/entities/entity.hpp" #include "core/types.hpp" +// Forward declaration — la definició completa s'inclou només al .cpp. +struct BulletConfig; + class Bullet : public Entities::Entity { public: Bullet() @@ -25,25 +27,25 @@ class Bullet : public Entities::Entity { // Override: Interfaz de Entity [[nodiscard]] auto isActive() const -> bool override { return is_active_; } - // Override: Interfaz de colisión (gameplay-level: PLAYAREA bounds-check) - [[nodiscard]] auto getCollisionRadius() const -> float override { - return Defaults::Entities::BULLET_RADIUS; - } + // Override: Interfaz de colisión (radi derivat al ctor des del shape). + [[nodiscard]] auto getCollisionRadius() const -> float override { return collision_radius_; } [[nodiscard]] auto isCollidable() const -> bool override { return is_active_; } + // Configuració associada (vàlida des del ctor — la bala l'agafa del BulletRegistry). + [[nodiscard]] auto getConfig() const -> const BulletConfig& { return *config_; } + // Getters (API pública sin cambios) [[nodiscard]] auto getOwnerId() const -> uint8_t { return owner_id_; } - // Posició al final del frame anterior, per a CCD segment-vs-cercle. [[nodiscard]] auto getPrevPosition() const -> const Vec2& { return prev_position_; } void desactivar(); private: - // Miembros específicos de Bullet (heredados: renderer_, shape_, center_, angle_, brightness_, body_). - // Inicializados en la declaración para que tanto el ctor por defecto como el que toma renderer - // dejen el objeto en estado coherente (proyectil inactivo, sin owner). + const BulletConfig* config_{nullptr}; // apunta al BulletRegistry; vàlid post-ctor + float collision_radius_{0.0F}; // derivat: shape.bounding_radius × scale × collision_factor + bool is_active_{false}; uint8_t owner_id_{0}; // 0=P1, 1=P2 - Vec2 prev_position_{}; // Posició al final del frame anterior (per a swept collision) + Vec2 prev_position_{}; // posició al final del frame anterior (swept CCD) }; diff --git a/source/game/entities/bullet_config.cpp b/source/game/entities/bullet_config.cpp new file mode 100644 index 0000000..0442a98 --- /dev/null +++ b/source/game/entities/bullet_config.cpp @@ -0,0 +1,80 @@ +// bullet_config.cpp - Implementació del parser de BulletConfig +// © 2026 JailDesigner + +#include "game/entities/bullet_config.hpp" + +#include +#include +#include +#include + +namespace { + + auto parseColor(const fkyaml::node& node, SDL_Color& out) -> bool { + if (!node.is_sequence() || node.size() != 3) { + return false; + } + const auto R = node[0].get_value(); + const auto G = node[1].get_value(); + const auto B = node[2].get_value(); + out = SDL_Color{ + .r = static_cast(R), + .g = static_cast(G), + .b = static_cast(B), + .a = 255}; + return true; + } + + auto parseShape(const fkyaml::node& node, BulletConfig::ShapeCfg& out) -> bool { + if (!node.contains("shape") || !node["shape"].contains("path")) { + std::cerr << "[BulletConfig] Error: falta 'shape.path'\n"; + return false; + } + const auto& s = node["shape"]; + out.path = s["path"].get_value(); + out.scale = s.contains("scale") ? s["scale"].get_value() : 1.0F; + out.collision_factor = s.contains("collision_factor") + ? s["collision_factor"].get_value() + : 1.0F; + return true; + } + + auto parsePhysics(const fkyaml::node& node, BulletConfig::PhysicsCfg& out) -> bool { + if (!node.contains("physics")) { + std::cerr << "[BulletConfig] Error: falta 'physics'\n"; + return false; + } + const auto& p = node["physics"]; + out.mass = p["mass"].get_value(); + out.restitution = p["restitution"].get_value(); + out.linear_damping = p["linear_damping"].get_value(); + out.angular_damping = p["angular_damping"].get_value(); + out.impact_momentum_factor = p["impact_momentum_factor"].get_value(); + return true; + } + + auto parseColors(const fkyaml::node& node, BulletConfig::ColorsCfg& out) -> bool { + if (!node.contains("colors") || !parseColor(node["colors"]["normal"], out.normal)) { + std::cerr << "[BulletConfig] Error: 'colors.normal' no és [r,g,b]\n"; + return false; + } + return true; + } + +} // namespace + +auto BulletConfig::fromYaml(const fkyaml::node& node) -> std::optional { + try { + BulletConfig cfg; + cfg.name = node.contains("name") ? node["name"].get_value() : "bullet"; + + if (!parseShape(node, cfg.shape)) { return std::nullopt; } + if (!parsePhysics(node, cfg.physics)) { return std::nullopt; } + if (!parseColors(node, cfg.colors)) { return std::nullopt; } + + return cfg; + } catch (const std::exception& e) { + std::cerr << "[BulletConfig] Excepció parsejant: " << e.what() << '\n'; + return std::nullopt; + } +} diff --git a/source/game/entities/bullet_config.hpp b/source/game/entities/bullet_config.hpp new file mode 100644 index 0000000..f63ef14 --- /dev/null +++ b/source/game/entities/bullet_config.hpp @@ -0,0 +1,41 @@ +// bullet_config.hpp - Configuració de la bala carregada des de YAML +// © 2026 JailDesigner +// +// Paral·lel a Player/EnemyConfig. Una sola instància a tot el joc (per ara); +// es comparteix entre totes les bales actives via BulletRegistry. + +#pragma once + +#include + +#include +#include + +#include "external/fkyaml_node.hpp" + +struct BulletConfig { + struct ShapeCfg { + std::string path; + float scale; + float collision_factor; + }; + + struct PhysicsCfg { + float mass; + float restitution; + float linear_damping; + float angular_damping; + float impact_momentum_factor; // factor de transferència bullet→enemic + }; + + struct ColorsCfg { + SDL_Color normal; + }; + + std::string name; + ShapeCfg shape; + PhysicsCfg physics; + ColorsCfg colors; + + static auto fromYaml(const fkyaml::node& node) -> std::optional; +}; diff --git a/source/game/entities/bullet_registry.cpp b/source/game/entities/bullet_registry.cpp new file mode 100644 index 0000000..f76bd6d --- /dev/null +++ b/source/game/entities/bullet_registry.cpp @@ -0,0 +1,37 @@ +// bullet_registry.cpp - Implementació del registre de bala +// © 2026 JailDesigner + +#include "game/entities/bullet_registry.hpp" + +#include +#include + +#include "core/entities/entity_loader.hpp" + +BulletConfig BulletRegistry::config; +bool BulletRegistry::loaded = false; + +auto BulletRegistry::load() -> bool { + auto yaml = Entities::EntityLoader::load("bullet"); + if (!yaml) { + std::cerr << "[BulletRegistry] Error: no s'ha pogut carregar bullet.yaml\n"; + return false; + } + auto cfg = BulletConfig::fromYaml(*yaml); + if (!cfg) { + std::cerr << "[BulletRegistry] Error: format invàlid a bullet.yaml\n"; + return false; + } + config = *cfg; + loaded = true; + std::cout << "[BulletRegistry] Configuració de bala carregada.\n"; + return true; +} + +auto BulletRegistry::get() -> const BulletConfig& { + if (!loaded) { + std::cerr << "[BulletRegistry] FATAL: get() abans de load()\n"; + std::exit(EXIT_FAILURE); + } + return config; +} diff --git a/source/game/entities/bullet_registry.hpp b/source/game/entities/bullet_registry.hpp new file mode 100644 index 0000000..410c2c7 --- /dev/null +++ b/source/game/entities/bullet_registry.hpp @@ -0,0 +1,26 @@ +// bullet_registry.hpp - Registre estàtic de la configuració de la bala +// © 2026 JailDesigner +// +// Una única instància per a tota la sessió. Es manté el patró registry +// (paral·lel a EnemyRegistry) tot i ser una sola entitat: si el dia de demà +// hi ha més tipus de bala (laser/plasma/etc.) només cal estendre-ho. + +#pragma once + +#include "game/entities/bullet_config.hpp" + +class BulletRegistry { + public: + BulletRegistry() = delete; + + // Carrega data/entities/bullet/bullet.yaml. Retorna false si falla. + static auto load() -> bool; + + // Accés a la configuració. Avorta amb log fatal si load() no s'ha cridat + // o ha fallat. + static auto get() -> const BulletConfig&; + + private: + static BulletConfig config; + static bool loaded; +}; diff --git a/source/game/scenes/game_scene.cpp b/source/game/scenes/game_scene.cpp index 92fc924..31303f3 100644 --- a/source/game/scenes/game_scene.cpp +++ b/source/game/scenes/game_scene.cpp @@ -15,6 +15,7 @@ #include "core/locale/locale.hpp" #include "core/system/scene_context.hpp" #include "core/system/service_menu.hpp" +#include "game/entities/bullet_registry.hpp" #include "game/entities/enemy_registry.hpp" #include "game/entities/player_config.hpp" #include "game/stage_system/stage_loader.hpp" @@ -72,6 +73,13 @@ GameScene::GameScene(SDLManager& sdl, SceneContext& context) std::exit(EXIT_FAILURE); } + // Carregar la configuració de la bala. Cal abans de construir el pool de + // bullets, ja que cada Bullet llegeix el registry al seu ctor. + if (!BulletRegistry::load()) { + std::cerr << "[GameScene] FATAL: no s'ha pogut carregar bullet.yaml\n"; + std::exit(EXIT_FAILURE); + } + // Inicialitzar naves: P1 amb el shape del YAML, P2 amb override visual. ships_[0] = Ship(sdl.getRenderer(), *player_config); // Jugador 1: nau estàndard ships_[1] = Ship(sdl.getRenderer(), *player_config, "ship2.shp"); // Jugador 2: interceptor amb ales diff --git a/source/game/systems/collision_system.cpp b/source/game/systems/collision_system.cpp index 730220c..d8995f4 100644 --- a/source/game/systems/collision_system.cpp +++ b/source/game/systems/collision_system.cpp @@ -8,6 +8,7 @@ #include "core/physics/collision.hpp" #include "core/types.hpp" #include "game/constants.hpp" +#include "game/entities/bullet_config.hpp" #include "game/entities/enemy_config.hpp" namespace Systems::Collision { @@ -80,7 +81,7 @@ namespace Systems::Collision { 0.0F, // sense velocity angular heretada 0.0F, // sense rotació visual heretada Defaults::Sound::HIT, - Defaults::Palette::BULLET, + bullet.getConfig().colors.normal, Defaults::Physics::Debris::TEMPS_VIDA, Defaults::Physics::Debris::ACCELERACIO, 1); // sense duplicat de segments @@ -96,7 +97,7 @@ namespace Systems::Collision { continue; } for (auto& enemy : ctx.enemies) { - if (!Physics::checkCollisionSwept(bullet.getPrevPosition(), bullet.getCenter(), Defaults::Entities::BULLET_RADIUS, enemy, AMPLIFIER)) { + if (!Physics::checkCollisionSwept(bullet.getPrevPosition(), bullet.getCenter(), bullet.getCollisionRadius(), enemy, AMPLIFIER)) { continue; } @@ -105,7 +106,7 @@ namespace Systems::Collision { // (m·v) multiplicat pel factor de transferència. Direcció = vector // velocity de la bala (cap a on viatjava). const Vec2 IMPULSE = bullet.getBody().velocity * - (bullet.getBody().mass * Defaults::Physics::Bullet::IMPACT_MOMENTUM_FACTOR); + (bullet.getBody().mass * bullet.getConfig().physics.impact_momentum_factor); enemy.applyImpulse(IMPULSE); const uint8_t SHOOTER = bullet.getOwnerId(); @@ -244,7 +245,7 @@ namespace Systems::Collision { continue; } - if (!Physics::checkCollisionSwept(bullet.getPrevPosition(), bullet.getCenter(), Defaults::Entities::BULLET_RADIUS, ctx.ships[player_id], AMPLIFIER)) { + if (!Physics::checkCollisionSwept(bullet.getPrevPosition(), bullet.getCenter(), bullet.getCollisionRadius(), ctx.ships[player_id], AMPLIFIER)) { continue; } @@ -253,7 +254,7 @@ namespace Systems::Collision { // de la bala a la nau ABANS de on_player_hit perquè tocado() // captura la velocitat per als debris (si no, queden quiets). const Vec2 BULLET_IMPULSE = bullet.getBody().velocity * - (bullet.getBody().mass * Defaults::Physics::Bullet::IMPACT_MOMENTUM_FACTOR); + (bullet.getBody().mass * bullet.getConfig().physics.impact_momentum_factor); ctx.ships[player_id].getBody().applyImpulse(BULLET_IMPULSE); ctx.on_player_hit(player_id); ctx.lives_per_player[BULLET_OWNER]++; @@ -280,12 +281,12 @@ namespace Systems::Collision { float min_y; float max_y; Constants::getPlayAreaBounds(min_x, max_x, min_y, max_y); - constexpr float R = Defaults::Entities::BULLET_RADIUS; for (auto& bullet : bullets) { if (!bullet.isActive()) { continue; } + const float R = bullet.getCollisionRadius(); const Vec2& pos = bullet.getCenter(); if (pos.x < min_x + R || pos.x > max_x - R || pos.y < min_y + R || pos.y > max_y - R) {